Clicking a Foundation layer opens it in view mode, which renders a different and equally flat set of views than the editor the handoff names. So the rail goes in the two containers rather than in the editors, and both modes get it from one adoption. View mode stays fully open because reading is its job; edit mode opens on one section, chosen by unresolved revision flags where they exist. The nine-layer cascade becomes a permanent one-line strip in the layer header, with the full diagram behind a hub button.
The problem
The screen named is not the screen you land on
Clicking a layer from the dashboard opens StandaloneLayerView, which starts with
isEditing = false (line 28). That renders LayerPreviewFullPage →
FullLayerPreviewContent → FullMissionPreview, and that last one is a
flat VStack(spacing: 24) running Statement, Core Values, Core Beliefs down the page
with no disclosure and no rail (LayerGenerationView.swift:1345).
EditMissionView is reached only after clicking Edit. The symptom is real; the file
named is the second one a visitor sees.
Behind that sits a larger duplication. The same nine layers render in three parallel hierarchies:
Three renderings of the same nine layers
Edit*View (9 layers, edit mode) · Full*Preview
(LayerGenerationView.swift:1345-2121, 7 layers, view mode) ·
*ReviewContent (FoundationReviewView.swift:349-1843, 9 layers,
browse-all). FullLayerPreviewContent already mixes the last two, borrowing
CognitiveReviewContent and PresenceReviewContent for the two layers it
lacks. Counting subsectionKeys and getLayerStatus, section identity is
written down in five places.
Neither "long" file is a long scroll
EditFeaturesView's body is FeaturesMasterDetailView —
a master/detail layout that already has a railed cluster list. Its bulk is
AddFeatureSheet (2212), EditFeatureSheet (2557),
SnapshotDetailSheet (3111). EditPersonasView's body has
three regions; its bulk is PersonaEditorSheet (1075) and
EcosystemMappingView (1392). Line count measured the file, not the canvas. The
layers that genuinely scroll are Mission (7 sections), Visual Design (7), Cognitive (5) and
Presence (5).
Approach
Mount the rail in the containers, not the content views
LayerPreviewFullPage and LayerEditorView are the two places every
layer passes through. Mounting there means two adoptions instead of eighteen, and it means view
mode gets the rail without touching the preview structs beyond adding anchors.
The progress strip and the section rail are one component
The handoff asks for a strip showing which sections have content and a rail with anchor-scroll. Built separately they would compete for the same click on the same page. One strip of chips carrying section icon, title, item count and a flag badge.
Counts rather than populated dots
"Core Values 5" carries more than a filled circle at identical cost, and an empty section simply shows no number, so emptiness reads without a second visual language.
Revision flags already know which section matters
NoteAnchor.fieldPath stores "<layer>.<sectionKey>" today
— "mission.coreBeliefs", "character.traits",
"visualDesign.colorPalette.colors" — and
manager.notes.activeNotes(for:) returns the unresolved ones. Mapping a flag to a
section needs no new data, which turns the disclosure default from an arbitrary pick into one
that points at work already identified.
The cascade becomes ambient
FoundationImpactDetector.dependencyWeights already encodes which layers feed
which, privately and incompletely. Promoted to a shared FoundationCascade, it
renders both a permanent one-line header strip and the full diagram, so the two cannot disagree
with the impact toast or with each other.
Decisions made for you
View mode never collapses; edit mode does
A preview exists to be read, so hiding its content behind navigation would defeat the one surface whose whole job is showing content. This also absorbs most of the regression the handoff warns about: anyone who scans a layer top to bottom does it in view mode, which is the default landing surface and stays fully open.
Flagged section first, then first populated
The handoff proposed defaulting Mission Statement open. A section carrying an unresolved
revision flag is a better candidate, because it names work already identified. First-populated
is the fallback, and the first section is the fallback to that. Reversible in one function in
FoundationSectionExpansionStore.
Expansion state lives in UserDefaults, keyed per project + layer + section
The handoff flagged this as ownerless. Not a JSON file under .claude/context/ —
that directory holds AI-generated foundation data that gets committed and synced, and disclosure
state is a per-machine preference. Matches
foundationSeedContentInjected_<projectId> in
FoundationFlowContainerView.swift:188. Reversible by swapping the store's backing.
The map no longer auto-presents
A screen shown once per project and then buried gets dismissed unread, and it answers the orientation question at the moment the user has least context for it. The permanent one-line cascade strip carries the orientation; the full diagram stays behind a hub button. Drops a first-run UserDefaults key from the design.
Character joins; Features and the Personas list stay as they are
EditCharacterView has the same flat four-section shape as Mission and the same
one-line fix; leaving it out preserves the inconsistency. EditFeaturesView is out
entirely — it already has a rail. In Personas only ecosystemMappingSection
collapses, because the personas list is what that page is for.
The work
Phase 1 — Section model, expansion storage, flag-aware defaults
5 tasks Prerequisite for Phases 2-5.- Create
FoundationSectionCatalogas the single source of section identity per layer - Point
FoundationStatusChecker.subsectionKeysat the catalog without changing its output - Add
FoundationSectionCatalog.sectionCounts(for:at:)returning item counts, not just populated flags - Add
FoundationSectionCatalog.flaggedSections(for:manager:)mapping revision flags onto sections - Create
FoundationSectionExpansionStorewith the flag-first default
Phase 2 — The shared section rail, mounted in the containers
4 tasks Two mount points instead of eighteen.- Build
FoundationSectionRail - Add Expand all / Collapse all to the rail's trailing edge, shown in edit mode only
- Add anchor scrolling via
ScrollViewReader - Mount the rail in
LayerPreviewFullPageandLayerEditorView
Phase 3 — View mode: the first-contact surface
3 tasks The screen a visitor actually lands on. Stays fully expanded.- Tag sections in the seven
Full*Previewstructs with catalog-keyed anchors - Tag sections in
CognitiveReviewContentandPresenceReviewContent - Add a section-anchor drift check
Phase 4 — Edit mode: disclosure in the three flat editors
4 tasks Mission, Character, Personas.- Wrap EditMissionView's seven sections in
ExpandableSectionbound to the store - Keep EditMissionView's empty-state banner above the rail and outside the disclosures
- Wrap EditCharacterView's four sections and bind them to the store
- Collapse only Ecosystem Mapping in EditPersonasView
Phase 5 — Migrate the five already-expandable editors onto the store
3 tasks Deferrable. Nothing depends on it and no one has reported the reset.- Replace local expansion
@Statewith store bindings in EditVisualDesignView and EditUIUXView - Replace local expansion
@Statewith store bindings in EditCodeView, EditCognitiveView, EditPresenceView - Verify the rail's chips match each migrated editor's actual sections
Phase 6 — The cascade: ambient line and full map
5 tasks Independent of Phases 1-5; can land in either order.- Promote the cascade edges out of
FoundationImpactDetectorinto a sharedFoundationCascade - Add a one-line cascade strip to both layer containers
- Build
FoundationMapViewas a native SwiftUI cascade diagram - Band Character, Visual Design and UI/UX together as the three "how" layers
- Add a Map button to the hub and make each node open its layer
Phase 7 — Build
2 tasks- Build clean with
xcodebuild -scheme "Code Raven" -destination 'platform=macOS' build - Verify the build product with
scripts/verify-build-product.sh
Manual Testing
15 checks Yours — the GUI cannot be driven from the terminal here.- Click a Foundation layer from the dashboard and confirm the rail appears in view mode before any Edit clickyou
- Confirm view mode stays fully expanded and nothing collapses thereyou
- Click each rail chip in view mode and confirm it scrolls that section to the topyou
- Confirm the rail chips show item counts and that empty sections show no numberyou
- Flag an item for revision in Mission, reopen the layer in edit mode, and confirm that section is the one expandedyou
- Resolve the flag, reopen a layer never touched before, and confirm the first populated section is expanded insteadyou
- Open Mission in edit mode on a project with no foundation data — confirm the empty-state banner sits above the rail and the first section is expandedyou
- Collapse everything in Mission, navigate to another layer and back, and confirm the collapsed state persistedyou
- Click Expand all, quit and relaunch, and confirm everything is still expandedyou
- Confirm Personas keeps its list expanded and only Ecosystem Mapping collapsesyou
- Confirm Features is untouched — master/detail and its cluster rail behave exactly as before, with no second railyou
- Confirm dashboard layer progress and the workflow pipeline stage report the same numbers as before Phase 1you
- Confirm the cascade strip names the correct upstream and downstream layers on all nine layersyou
- Open the map from the hub — confirm nine layers, correct arrows, the three "how" layers banded, accurate counts, and that clicking a node opens that layeryou
- Confirm the map never auto-presentsyou
Risks & edge cases
subsectionKeys feeds the dashboard and the workflow pipeline
A one-key difference between the catalog-derived list and the current literals shifts reported layer progress and pipeline stage detection, with nothing on screen saying so. The Phase 1 debug assertion is the guard; the before/after comparison in Manual Testing is the check.
Sections now live in two hierarchies
A section added to an editor but not to the matching preview produces a rail chip that scrolls nowhere. The Phase 3 drift check catches it in debug builds only, so a release build would ship the dead chip silently.
fieldPath parsing is positional
It assumes "<layer>.<sectionKey>…". Every call site follows that
shape today and nothing enforces it, so a future anchor with a different prefix maps to no
section and quietly loses its flag-driven expansion.
Edit mode still costs a click for top-to-bottom scanners
Much reduced now that view mode is the landing surface and stays open, and the persisted store means Expand all is pressed once per layer rather than once per visit. Reduced, not erased.
UserDefaults keys outlive their projects
Removing a project leaves its expansion keys behind. Harmless in size, with no cleanup hook;
FoundationManager.clearCache is where one would go if it mattered.
The cascade edges are wrong in a way the diagram will publish
.features → .code and .features → .uiux run against the documented
1-9 order, and .uiux has no outgoing edges at all. Correcting them is in scope; what
the true edges should be may not have a settled answer.
The ambient strip adds chrome to a busy header
LayerPreviewFullPage's header already carries back, title, context menu, status
and the Features utility row. One more line is the cheapest form of persistent orientation, and
it may still be one line too many.
Out of scope
Collapsing the three rendering hierarchies into one
Full*Preview and *ReviewContent already overlap —
FullLayerPreviewContent borrows two of the latter. Unifying them is the right
eventual move and a large enough diff to bury this change. Named so the duplication is a known
deferral.
EditFeaturesView
Already has a rail and a master/detail layout. A second strip would compete with the cluster list for the same click.
Splitting the large editor files
Extracting the sheets from EditFeaturesView and EditPersonasView has
no user-visible outcome.
Reconciling getLayerStatus's missing-element strings with the catalog
Those free-text lists feed generation prompts; rewriting them is a generation-path change.
Restructuring the nine layers
Whether Character, Visual Design and UI/UX should be three separate layers is a different question from making the current cascade legible.
Final review
Where it is strong. Checking which screen actually opens moved the work off a
surface most visits never reach, and mounting the rail in the two containers turned eighteen
adoptions into two. The reuse ratio is high: ExpandableSection, the raw-JSON
populated logic, NoteAnchor.fieldPath, layer.description and
dependencyWeights all exist and all get used rather than rebuilt. Four new files.
Splitting the behavior by mode — reading surfaces stay open, editing surfaces collapse — resolves
the regression the handoff worried about instead of merely mitigating it.
Where it is weakest. Phase 1's subsectionKeys change is the one
piece of real risk and it buys structure rather than user-visible value; the catalog could
duplicate the keys at the cost of a sixth hardcoded list. Phase 5 is five files edited so
disclosures remember, which no one has reported wanting. The flag-first default is reasoned but
untested, and it does nothing on a project that has never used Flag for Revision, which may be
most of them. The three-hierarchy duplication is named and deferred, so this plan adds anchors to
a structure it also says should not exist in that form.
What I would adjust if pushed. Ship Phases 1-4 and 6, then decide whether
Phase 5 earns its diff. If the Phase 1 assertion proves awkward, let the catalog own its keys and
leave FoundationStatusChecker untouched — the duplication is ugly and inert. If the
ambient strip crowds the header in practice, move it under the rail rather than dropping it.
Up from 82 on the first draft. The view-mode finding was verified in source rather than assumed, the flag-to-section mapping was confirmed against every existing NoteAnchor call site, and the container mount point removes most of the adoption surface. Held back by the subsectionKeys refactor's silent-drift risk, a flag-first default that does nothing on projects which have never flagged anything, and cascade edges being corrected in the same breath as they are first drawn.