Repository navigation
Description
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,
copybinding); 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
kyle-sexton commentedon Nov 3, 2026
Claimed by the planning-chain consolidation session (epic #12646) for an overnight build through merge. Other lanes: please skip this issue.
github-actions commentedon Nov 3, 2026
github-actions commentedon Nov 3, 2026
melodic-automation-lanes commentedon Nov 3, 2026
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:
Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/41575140916
melodic-automation-lanes commentedon Nov 3, 2026
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-readystarted, before it did any work; that run ended at 2026-11-03T04:45:19Z. work-unblock has now removed and addedagent-readyback 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 addedagent-readyor a run last reported, work-unblock waits longer before each one, up to 24 hours.github-actions commentedon Nov 3, 2026
fix(rendered-views): match mediums and lanes on own keys only