Skip to content

planning: name the stale ancestor, not the direct parents, when the interview page marks a question upstream-pending #5922

Open

Description

@kyle-sexton

Problem

On the /planning:interview page, a question in the upstream-pending state is dimmed and labelled Waiting on <its direct dependsOn ids>. The server sets that state when any ancestor further up the chain is stale (server.py, upstream()), but the page names only the direct parents (index.html, the rail chip 'Waiting on ' + upOf(q).join(", ") and the detail lockban).

Live case: Q125 depends on Q106 and Q53, and both are answered. Q106 went stale because the user accepted it before accepting its prerequisite Q88. The rail showed Q125 dimmed with Waiting on Q106, Q53, and the user read it as locked: "One question I see in the UI is locked … I can't click on it". The answer controls were in fact enabled, and the detail banner says "You can still answer", but nothing told the user which question to act on.

Expected

  • The chip and banner name the stale ancestor(s) that cause the state (here Q106), not every direct parent.
  • They say what clears it: "Reconfirm Q106 (under Review), or answer this one anyway".
  • A dimmed rail row still reads as clickable: the dim marks waiting, not locked.

Suggested fix

  • Return the stale ancestor ids from the server alongside the state (upstream() already finds the first stale ancestor), for example upstreamStale: ["Q106"].
  • Use them in the chip (Waiting on Q106) and the banner, with a link that selects that question.

Acceptance criteria

  • With Q125 → Q106 (stale) and Q53 (settled), the chip reads Waiting on Q106 and the banner links Q106 with the reconfirm hint.
  • Reconfirming Q106 clears Q125's upstream-pending state without a reload.

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 2, 2026
  2. kyle-sexton commented on Oct 4, 2026

    @kyle-sexton
    ContributorAuthor

    This was generated by AI during triage.

    Routing: delegable, work-class: scoped (C3, bug-fix), priority: medium (the user read a dimmed, answerable question as locked). Verified against current code: the server marks a question upstream-pending when any ancestor is stale, but the page names only the direct parents in both the rail chip and the detail banner.

    Basis: verified, plugins/planning/surface/server.py:369-379 (upstream() walks every ancestor and returns True on the first stale one, no ids), :392-393 (state set), plugins/planning/surface/index.html:737 (chip 'Waiting on ' + upOf(q)), :1068 (banner upOf(q)), :681 (upOf = direct dependsOn). Precedent for a computed per-question field: upstreamChanged, declared at plugins/planning/surface/schema/questions.schema.json:335, set at server.py:862, kept out of persisted data per test_server.py:2084.

    Serialize: this edits the same rail chip and detail banners as #5917, #5908 and #5910, and the rest of the interview-page set (#5815, #5911, #5913, #5916). Dispatch one at a time.

    Agent Brief

    Type: Bug
    Summary: When the interview page shows a question as upstream-pending, name the stale ancestor(s) that cause it, say what clears it, and keep the row reading as clickable.

    Current behavior:
    The server sets upstream-pending on a question when any ancestor further up its dependsOn chain is stale. The page's rail chip reads Waiting on <direct parents> and the detail banner lists the direct parents too. When the direct parents are all answered and the stale question sits further up (or is a direct parent among settled ones), the user cannot tell which question to act on, and the dimmed row reads as locked although the answer controls work.

    Desired behavior:

    • The server returns, with each upstream-pending question, the ids of every stale ancestor reachable through dependsOn (not only the first one found), in a deterministic order (for example, nearest first, ties by id). Name the field in the style of upstreamChanged (the issue suggests upstreamStale).
    • The field is computed per response like upstreamChanged: declared in the questions schema as a server-computed field, and never written into the persisted questions or responses data.
    • The rail chip reads Waiting on <stale ancestor ids>; the detail banner names the same ids, each a link that selects that question, with a hint of what clears it: reconfirm the stale question (it appears under Review), or answer this one anyway.
    • The dimmed rail row still reads as clickable: the dim marks waiting, not locked (keep it a normal, focusable, hoverable row).
    • When the stale ancestor is reconfirmed, the dependent's upstream-pending state and chip clear on the page's next refresh, without a manual reload.

    Key interfaces:

    • The server's state computation (upstream() and the function that returns states and upstreamChanged).
    • The questions schema's computed-field entries.
    • The page's rail-item chip and the detail view's upstream-pending banner.

    Acceptance criteria:

    • With Q125 depending on Q106 (stale) and Q53 (settled), the chip reads Waiting on Q106 and the banner links Q106 with the reconfirm hint.
    • With a stale ancestor two levels up (Q125 → Q106 answered → Q88 stale), the chip names Q88, not Q106.
    • With two stale ancestors, both are named, in the documented order.
    • Reconfirming the stale ancestor clears the dependent's upstream-pending state without a reload.
    • The new field validates against the questions schema and is never persisted; server and surface tests pass, with new server tests for the cases above.

    Out of scope:

  3. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 4, 2026
  4. kyle-sexton commented on Oct 4, 2026

    @kyle-sexton
    ContributorAuthor

    This was generated by AI during work-loop.

    Ratify autonomous dispatch of this C3 bug-fix item?

    Classification: work-class: scoped (C3), bug-fix-shaped (confirmed at triage). Intended dispatch: the work-loop worker lane, through /work-items:work, to a draft PR the lane never merges, serialized with the other interview-page fixes. First drain is not complete and no item-level ratification exists, so it is queued here. Ratify through /work-items:attend-queue.

    Lane: work-loop, instance melo-worker-1, cycle 2.

  5. added
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 4, 2026
  6. kyle-sexton commented on Oct 11, 2026

    @kyle-sexton
    ContributorAuthor

    Delivered on branch feat/planning-interview-page-batch in draft PR #7182, one commit for this issue (with Closes #5922).

    The server adds a derived, never-persisted upstreamStale list (every stale ancestor, nearest first, ties by id); the chip reads Waiting on <them>, the banner links them with "Reconfirm ... (under Review), or answer this one anyway", and the row reads as waiting, not locked.

    Verified: New TestUpstreamStaleNamesTheCause in test_server.py (direct, two-level, two stale, reconfirm clears, never written); ui_c and ui_page phase 4 check the chip and banner. The full surface.test.sh was last run in full before the final page commits; the later commits were checked with the ui_page phases. Its one standing failure (AC34 "user name found") is an environment artifact that also fails on main.

    Left for a human: the PR stays a draft until Kyle flips it to ready, which starts the CI and review lanes.

    The needs-human label was left for Kyle to clear.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: mediumReal value, no hard deadline; normal backlog flow.status: readyTriaged, unblocked, and fully specified; eligible to pick up.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions