Skip to content

feat(persona): give a face a cursor, so its masked values can be read - #288

Merged
stormer78 merged 1 commit into
mainfrom
feat/faces-reveal
Sep 8, 2026
Merged

feat(persona): give a face a cursor, so its masked values can be read#288
stormer78 merged 1 commit into
mainfrom
feat/faces-reveal

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Reported from the seat: "in Faces, we have some of these as able to be shown, yet showing as masked", and "be good to have a key (v) to press to show values easily".

Opening a face showed its claims masked with no way to read one, and told the holder to go and find the value among their attributes instead:

Some values are masked by what they are. Read one of them among your attributes, where they open one at a time.

That is a real answer to a question nobody asked. Someone is looking at this face precisely because they want to know what this face shows; sending them to another tab to reconstruct it by eye is the pane declining to answer.

Why the old refusal no longer holds

It was reasoned, and the reasoning is what changed. It was never "a face must not be revealed" — it was:

a face has no cursor over its claims, so the only reveal this view could offer is the blanket one the mask exists to avoid

That is a statement about the pane, not about the data. Give the detail view a cursor and the objection is gone, because "the selected claim" becomes a thing a holder can name.

What it does now

The face detail works exactly like the attributes tab, on the same terms:

  • / move a cursor over the claims
  • s lifts the mask on the selected one; a second press puts it back
  • the grant is one claim, and only while it is also the selected row — the render checks the pairing as well as the handler clearing it, so a grant that outlived its row cannot open a claim nobody chose
  • moving the cursor, closing the face, or leaving the tab each ends it
  • opening a face starts at the top with nothing revealed, rather than inheriting an index that now names a different claim in a different face

A masked row also says it is masked, for the reason the attributes tab already does: •••••••• and (no value) are the same shape on a row, and a holder who reads the first as the second concludes the face shows nothing. The — s to show tail follows the cursor, so the key is only offered where it would work.

On the v key

v already exists — on Your attributes, where the header reads values shown — v to hide. It is deliberately not added here, and that is not an omission:

  • on the attributes tab v is a network question. A listing fetched without values does not hold them, which is what makes it an opt-in rather than a blindfold.
  • a face detail is resolved in full when it is opened. Every value is already in hand and the mask is the only thing between it and the screen.

A v here would be a display flag pretending to be an escalation. s is the honest key for what this view can actually offer.

Types

ResolvedClaim::revealed_value is the new counterpart to display_value. It stays a separate method rather than a reveal: bool, for the reason its pool sibling gives: reading a masked value in the clear should be something a call site had to name. Its doc records why the counterpart used to be absent and what changed, so the next reader meets the argument rather than a gap.

Tests

The reveal opens only the selected claim; a grant that no longer names the selected row opens nothing; the key toggles; a reveal past the end or with no face open does nothing; every exit puts the mask back; opening a face starts closed and at the top. At the core level: a masked claim reads back whole only when asked for, and a stale one still says it is stale under a reveal.

The test that encoded the old decision (a_face_masks_its_values_and_says_where_to_read_one) is updated rather than deleted, and its doc says what changed and why.

Gate

cargo fmt --all; cargo clippy --workspace --all-targets -- -D warnings; RUSTDOCFLAGS="-D warnings" cargo doc; cargo test --workspace; --no-default-features checks.


Does not address the other two findings from the same session — the (no value) rows (which #286 should already have fixed by sending includeSensitive; needs confirming against a live VTA) and wears: nothing, which is still waiting on a decision about which context a membership's face belongs to.

Opening a face showed its claims masked with no way to read one, and told
the holder to go and find the value among their attributes instead. That is
a real answer to a question nobody asked: someone is looking at *this* face
precisely because they want to know what *this* face shows, and sending them
to another tab to reconstruct it by eye is the pane declining to answer.

The old refusal was reasoned, and the reasoning is what changed. It was not
"a face must not be revealed" — it was "a face has no cursor over its claims,
so the only reveal this view could offer is the blanket one the mask exists to
avoid". That is a statement about the pane, not about the data. Give the
detail view a cursor and the objection is gone, because "the selected claim"
becomes a thing a holder can name.

So the face detail now works exactly like the attributes tab, on the same
terms:

- `↑`/`↓` move a cursor over the claims.
- `s` lifts the mask on the selected one, and puts it back on a second press.
- The grant is one claim, and only while it is also the selected row — the
  render checks the pairing as well as the handler clearing it, so a grant
  that outlived its row cannot open a claim nobody chose.
- Moving the cursor, closing the face, or leaving the tab each ends it.
  Opening a face starts at the top with nothing revealed, rather than
  inheriting an index that now names a different claim in a different face.

A masked row also says it is masked, for the reason the attributes tab
already does: `••••••••` and "(no value)" are the same shape on a row, and a
holder who reads the first as the second concludes the face shows nothing.
The "s to show" tail follows the cursor rather than sitting on every masked
row, so the key is offered where it would work.

There is deliberately no `v` here. On the attributes tab `v` is a *network*
question — a listing fetched without values does not hold them — but a face
detail is resolved in full when it is opened, so every value is already in
hand and the mask is the only thing between it and the screen. A `v` would be
a display flag pretending to be an escalation.

`ResolvedClaim::revealed_value` is the new counterpart to `display_value`,
and stays a separate method rather than a `reveal: bool` for the reason its
pool sibling gives: reading a masked value in the clear should be something a
call site had to name. Its doc records why the counterpart used to be absent
and what changed, so the next reader meets the argument rather than a gap.

Tests: the reveal opens only the selected claim; a grant that no longer names
the selected row opens nothing; the key toggles; a reveal past the end or with
no face open does nothing; every exit puts the mask back; opening a face
starts closed and at the top; and at the core level a masked claim reads back
whole only when asked for, while a stale one still says it is stale under a
reveal. The test that encoded the old decision is updated rather than deleted,
and says what changed.

Gate: fmt, clippy -D warnings, rustdoc -D warnings, cargo test --workspace,
and --no-default-features.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 requested a review from a team as a code owner September 8, 2026 12:43
@stormer78
stormer78 merged commit 0e47428 into main Sep 8, 2026
14 checks passed
@stormer78
stormer78 deleted the feat/faces-reveal branch September 8, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant