Skip to content

feat(rendered-views): one medium resolver with the publish trust walk and fallback chain #12649

Description

@kyle-sexton

Parent

Source: PLAN Phase 1, topic planning-chain-consolidation. Epic: #12646.

Depends on

  • none: on the frontier now

Outcome

Every view lane resolves its medium through one resolver on the ADR 0075 scopes: the argument decides alone; a terminal or file value from userConfig, team or personal override keeps the view local; a publishing value counts only from an untracked, gitignored personal override or userConfig; with no scope set the plugin default applies; a team file never publishes. The retired rendered-views files produce a notice, never a value. Each lane prints one report line naming the medium used and what decided it.

Demo: Run the resolve CLI in a repo whose team file says artifact and userConfig says file: it prints file and names userConfig; remove userConfig and it prints the plugin default, never artifact from the team file.

The Brief (Problem, Goal, Constraints) is in the epic body.

Load-bearing assumptions

Assumption Source Falsifying check Response if false
The owner's machines set medium hosted in the review plugin userConfig only; revisit if the owner wants PR digests local Brief Captured assumptions (Q30) Phase 1 Sanity Check (review lane resolves hosted from userConfig) Minor
No team rendered-views.yaml in org repos; revisit if others work in org repos with Artifacts on Brief Captured assumptions (Q31) Phase 1 Sanity Check (org code search finds no docs/conventions/rendered-views.yaml) Minor
The cascade's file reader and schema check can be reused per scope without changing resolve() for its four existing callers plan Phase 1 Sanity Check (existing config-cascade tests pass) Moderate: add a per-layer export to the cascade and bump its carriers

Decisions

  • Q14: medium moves onto ADR 0075 scopes; ~/.claude/rendered-views.md is no longer read.
  • Q17: One fallback chain; every view lane names the medium used and the fact that decided it.
  • Q29/Q50: Retired rendered-views files produce a notice once per view-lane plugin per project, never their value.
  • Q31: No team rendered-views.yaml in org repos for now.
  • Q39: The retired-file notice names the lane plugin's medium userConfig, or docs/conventions/rendered-views(.local).yaml; shown-once state per plugin keyed by project and file hash.
  • D6: The config-cascade contract stops marking rendered-views conforms when it conforms.

Key interfaces

Module layout

Module Owns Public contract Consumers
View settings medium resolution, publish trust filter, retired-file notices C1 resolveMedium every view lane
Medium registry medium entries, fallback chain order, reachability probes, publish gate call C2 MediumEntry view settings, renderer
View renderer view kinds (plan, brainstorm, design), templates, redraw binding, shared chrome C2 render, C5 every view lane
Surface registry interview surfaces (terminal, page, shared session) C2 SurfaceEntry, resolveSurface planning:interview
Page input lock and comment events, missed-input sweep C4 review pages, interview pages

Dependency direction: skills -> view settings and renderer -> registries -> external providers (Artifact service, hosted publisher, tunnel), each contained in one registry entry. Registries never call skills. Each shared module has one canonical copy synced into each plugin (ADR 0019).

Contracts

resolveMedium(request) -> Resolution
request:    lane, argument?, userConfig?, projectRoot?, teamRef?
Resolution: preferred?, medium, decidedBy, notices[]
Rules: the argument decides alone. Each scope's own value is read separately (cascade file reader
and schema check, not resolve()'s merged output; team file optionally from teamRef). terminal/file
from any scope keeps the view local. Else the first publishing value from a trusted scope wins:
personal override (untracked and gitignored only), then userConfig. With no scope deciding, the
plugin default applies (trusted: the installed plugin); "any scope" never includes the plugin default. A team file never publishes; an invalid value is ignored without dropping another
scope's value; auto equals unset. Retired files (~/.claude/rendered-views.md,
.claude/rendered-views*.md) produce notices, never values.
CLI: view-settings resolve --lane <id> --option <user_config.medium> [--argument <m>] [--team-ref <ref>]

MediumEntry   { id, publishes, reachable(context) -> {ok, reason}, deliver(view, context) -> {location}, reportName }
SurfaceEntry  { id (terminal | page | shared-session; a shared-session variant is a new entry),
                available(context) -> {ok, reason}, start(session) -> handle, reportName }
ViewKindEntry { id, parse(record) -> data, template, tier }
Lane registration: { laneId, kinds[], hostedCapable }
Fallback chain (medium registry): artifact, file, terminal; hosted read as artifact unless hostedCapable.
Skills call resolveMedium, render(laneId, kind, record), resolveSurface; they never name a medium,
surface or kind value in their own prose.

Report line: view: <medium> (<decidedBy>), plus one line per notice.

PageInput { kind: lock | unlock | comment, recordId, choiceId?, optionId?, text?, at }
- untrusted data; the session reads only these fields; a lock is written to the record, never approval
- delivery: file via the session bridge; terminal via the conversation; Artifact via a
  "Copy choices as prompt" control (existing runtime copy binding) the owner pastes; the page says
  live lock needs the local page
- unhandled input is collected at the start of the owner's next message

Redraw binding: a choice names the example blocks it selects; client-only, no network, no storage.

Conventions followed

  • docs/adr/0075 (config cascade) via lib/config-cascade.mjs; docs/adr/0019 (shared copies, scripts/shared-copies.txt, sync-shared-copies.sh); docs/contracts/rendered-views/README.md:813-816 (publish trust); lib/view-runtime.js (hash-pinned runtime, copy binding); lib/session-bridge (loopback page events); docs/standards/design-for-change.md:26 (add a variant by addition); plugins/visualization/reference/html-chrome.html (chrome tokens, accessibility floor).
  • Net new: the three registries and the lane registration (no existing pattern holds a variant set for views); the redraw binding (none exists in lib/view-runtime.js); the PageInput shape outside the shared session; a design view kind in the builder.
  • Challenges a pattern: per-skill medium lists in ~20 skills and four unsynced rendered-view.md copies (single source of truth); lib/publish-gate.mjs trustedMedium and review's digest-policy.mjs resolveMedium as two resolvers with different rules; the 1:1 interview page's own settings chain (.claude/interview-surface.json) outside ADR 0075; docs/contracts/config-cascade/README.md:659 marking rendered-views "conforms".

Done when

  • AC4: medium resolves through ADR 0075 precedence; neither ~/.claude/rendered-views.md nor .claude/rendered-views*.md is read as a settings layer (tests in the view-settings test file pass, including: team artifact + userConfig file gives file; team invalid + userConfig file gives file; team artifact alone never publishes; tracked personal override artifact never publishes; plugin default artifact publishes when no scope decides; review resolves hosted from userConfig; team file read from a git ref).
  • AC7: the contract holds one fallback chain (artifact, file, terminal; hosted read as artifact except PR digests) and every lane prints the report line.
  • AC14: each retired file yields one notice per plugin per project naming its value and destination, never applied; an unchanged file does not notice twice.
  • The publish-gate and config-cascade tests pass; the shared-copies script passes --check and --check-bump; each synced view-settings copy imports.
  • A git grep for rendered-views(.local).md outside the resolver and its tests finds only changelog lines, and the skills and evals of the plugins the per-plugin slices migrate (adhd, ai-briefing, architecture, codebase-health, debugging, discovery, education, overengineering, prototype, work-items: refactor(adhd): resolve the view medium through the shared resolver #12668-refactor(discovery): resolve the view medium through the shared resolver #12741 under Epic: consolidate the planning chain #12646, which move those skills onto the resolver per Q14; the repo-wide grep is checked in chore(vocabulary): retire rung, default ladder, overlay and the other clashing senses with a checker #12750 after they merge).
  • The config-cascade contract no longer marks rendered-views conforms incorrectly (D6).

Out of scope

  • Migrating skills outside review, visualization and planning onto the CLI (later slices)
  • Adding a team rendered-views.yaml to any org repo (Q31)

Activity

  1. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Nov 3, 2026
  2. kyle-sexton commented on Nov 3, 2026

    @kyle-sexton
    MemberAuthor

    Claimed by the planning-chain consolidation session (epic #12646) for an overnight build through merge. Other lanes: please skip this issue.

  3. github-actions commented on Nov 3, 2026

    @github-actions
    Contributor
    No description provided.
  4. github-actions commented on Nov 3, 2026

    @github-actions
    Contributor
    No description provided.
  5. melodic-automation-lanes commented on Nov 3, 2026

    @melodic-automation-lanes
    Contributor

    The work-resolve-issue lane stopped on this issue (scan-refused-patch): the scan job refused the run's patch for its size, and the run job's scan of it found no credential shape or binary hunk, so a fresh run may make a smaller one.
    It keeps agent-ready, and work-unblock starts it again (retry 1 of 2).

    In the run's words:

    The scan job refused the run's output, so nothing was published: too-large: the patch was over its 262144-byte cap. The run's log names its size.

    Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/41575140916

  6. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Nov 3, 2026
  7. self-assigned this
    on Nov 3, 2026
  8. melodic-automation-lanes commented on Nov 3, 2026

    @melodic-automation-lanes
    Contributor

    This records a re-fire of an earlier refusal, not a new refusal. The lanes kill switch stopped run 41578492056, the work-resolve-issue run that agent-ready started, before it did any work; that run ended at 2026-11-03T04:45:19Z. work-unblock has now removed and added agent-ready back to start a fresh run. A kill-switch refusal is no fault in this issue or the lanes, so it never counts against later re-fires; after limits.limit-stops-per-issue of them since a person last added agent-ready or a run last reported, work-unblock waits longer before each one, up to 24 hours.

  9. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Nov 3, 2026
  10. github-actions commented on Nov 3, 2026

    @github-actions
    Contributor
    No description provided.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.

Fields

No fields configured for Feature.

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions