Skip to content

feat(join): a route whose first step the join can take is not blocked - #351

Merged
stormer78 merged 1 commit into
mainfrom
worktree-join-route-first-step
Sep 19, 2026
Merged

stormer78 merged 1 commit into
mainfrom
worktree-join-route-first-step

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

From running the join against Linux Kernel: Apply for vetting was greyed out with "applying needs a persona — Press n to create one", so on a community that vets, the one route the community is actually telling you about looked like the one you could not use — and the way forward looked like configuration work to go and do elsewhere.

The distinction the code was missing

Who has to supply the missing thing.

A persona OpenVTC can make one → the route is takeable, it just begins a step earlier
An invitation somebody else has to have issued it → the route really is shut

RouteOption.blocked: Option<String> becomes RouteState:

pub enum RouteState {
    Ready,
    FirstStep(String),   // take it; it starts with this, then proceeds
    Blocked(String),     // it cannot be taken, and why
}

Only Blocked is drawn dim. available() is true for FirstStep — that is the
whole point of it.

What you see now

The vetting route is live, and carries a line under it:

▸ Apply for vetting              no application yet
    First: you have no persona yet, so this starts by creating one — every
    card is signed by the DID you join with

Stated in the second person and in order — what happens next, not a refusal
dressed up. Enter opens the create-persona overlay (#348), and when it closes
having produced a persona the join takes the route it interrupted, rather
than returning you to the list to press the same key again. resume_route is
what that reads from.

An overlay dismissed with Esc leaves the route as it was — the row still saying
what it starts with — because the resume only fires when the step actually
produced what the route was waiting for.

Structure

take_route is split out of the keypress so the key and the resume go through
one path. The join loop cannot send itself actions (StateHandler has no
action_tx — it receives), so the resume calls the method directly rather than
re-dispatching, and the overlay is opened through the shared pure nav reducer.

N stays bound and stays in the footer, but is no longer presented as the way
past a blockage; it is now for someone who wants a second persona before
choosing.

Testing

  • cargo fmt --all, cargo clippy --all-targets --all-features — clean
  • cargo test --all-features -- --include-ignored — all green
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features — clean

Two assertions inverted with the behaviour, which is the honest summary of
the bug:

  • applying_needs_a_persona_but_an_open_request_does_not asserted
    !available() on the vetting route. That assertion was the bug, written
    down and passing.
  • applying_without_a_persona_points_at_the_key_here checked the blocked
    sentence named n; it now checks the route is takeable and announces its
    first step.

New: only_what_the_join_cannot_supply_is_blocked pins the line between the two
states, and a_route_that_starts_with_a_step_says_what_it_starts_with pins that
the note renders.

Not covered

The resume itself — overlay closes, join carries on into the application — has
no test, for the same reason #348's mint has none: it needs a VTA. That is the
part to watch on the live run, and the failure shape is landing back on the
routes list instead of in the application.

Applying for vetting without a persona was greyed out with "create one
under My Identity". On a community that vets, that made the one route the
community is actually telling you about look like the one you could not
use — and made the way forward look like configuration work to go and do
somewhere else, which is exactly what a join flow should absorb.

The distinction the code was missing is who has to supply the missing
thing. A persona is something OpenVTC can make for you, so a route that
needs one is a route you can take; it just begins a step earlier. An
invitation is a credential somebody else has to have issued you, so
holding none really is a shut door — there is no step that would produce
one. `RouteState` names the three cases (`Ready`, `FirstStep`, `Blocked`)
and only the last is drawn dim.

So the vetting route now carries "First: you have no persona yet, so this
starts by creating one", stated as what happens next rather than as a
refusal dressed up, and Enter on it opens the create-persona overlay.
When the overlay closes having produced a persona, the join takes the
route it interrupted — `resume_route` is what "carries on" reads from —
rather than returning to the list for the same keypress a second time. An
overlay dismissed with Esc leaves the route as it was, which is the row
still saying what it starts with.

`take_route` is split out of the keypress so both the key and the resume
go through one path; the loop cannot send itself actions, so the resume
calls it directly rather than re-dispatching.

`N` stays bound and stays in the footer, but is no longer offered as the
way past a blockage — it is now for someone who wants a second persona
before choosing. Two assertions inverted with the behaviour, including
`!available()` on the vetting route, which was the bug stated as a test.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 requested a review from a team as a code owner September 19, 2026 06:39
@stormer78
stormer78 merged commit 0adc682 into main Sep 19, 2026
15 checks passed
@stormer78
stormer78 deleted the worktree-join-route-first-step branch September 19, 2026 06:46
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