26 tasks · 7 phases · 15 manual checks · ~20 files · 2026-08-26

Foundation Layer Navigation: Section Rail, Flag-Aware Disclosure, and the Cascade

One section rail mounted in the two layer containers so it serves view and edit mode alike, disclosure defaults driven by unresolved revision flags, and the layer cascade made ambient instead of a one-time modal. Revised after finding that the screen the handoff names is the second one a visitor sees.

26Tasks
7Phases
4New files
3Render hierarchies
The gist

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

Premise correction

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.

Premise correction

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

Judgment call

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.

Judgment call

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.

Judgment call

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.

Judgment call

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.

Judgment call

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 FoundationSectionCatalog as the single source of section identity per layer
  • Point FoundationStatusChecker.subsectionKeys at 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 FoundationSectionExpansionStore with 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 LayerPreviewFullPage and LayerEditorView

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*Preview structs with catalog-keyed anchors
  • Tag sections in CognitiveReviewContent and PresenceReviewContent
  • 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 ExpandableSection bound 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 @State with store bindings in EditVisualDesignView and EditUIUXView
  • Replace local expansion @State with 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 FoundationImpactDetector into a shared FoundationCascade
  • Add a one-line cascade strip to both layer containers
  • Build FoundationMapView as 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

Load-bearing

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.

Edge case

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.

Edge case

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.

Regression

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.

Edge case

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.

Surfaces a question

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.

Tradeoff

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.

86/100

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.