hosystem Engagements

ho-06.4 — palana palette: the semantic color-token layer

created 2026-07-11
status complete
type ho-document
project sharibako
ho 06.4
kamae 5
shape ha
splits-from ho-06
agent-tasks

    Give the Workshop a real palette. Today the app leans on the raw macOS system accent (usually blue) and scatters .red / .green / .secondary across the views. This replaces that with a small named token layer that maps the pālana design system palette to semantic roles — accent, drift, in-sync, ink text, surfaces — wired through the existing AppAppearance light/dark/system override.

    It is a foundation ho: it changes no behavior and adds no feature. It exists so the upcoming right-side-panel ho (the failed-at-gate chrome from ho-06.2, owed ) has a clean panelGround and a coherent accent to build on, and so Sharibako and pālana read as siblings rather than two apps that happened to pick similar greens.

    Numbering. Provisional 06.4, to be ratified in the owed K4 overview pass (the panel, multi-root management, and first-run/ingest hos are all still unplaced). The panel ho follows as 06.5 there. This number lets the work start now; it does not pre-empt the K4 pass.

    Out of scope:


    Phase 1 —

    Four decisions, ratified 2026-07-11.

    Decision 1 — Nine semantic tokens, not a palette dump

    The view code should never name a hue. It names a role. The token set:

    Token pālana source Role in Sharibako
    accentMoss accent (moss) The one interactive color — tint, selection, link icon, CTA wash
    drift alarm (rust) Drift, errors, failures, destructive intent
    inSync moss family (own tuned tone) The affirmative — status/success pulse. A cleaner/brighter moss than the accent (gate-tuned, see ); still moss, no green traffic-light
    ink ink Primary text
    inkSecondary inkFaint Secondary facts — the workhorse .secondary replacement
    inkTertiary inkFaint (fainter) Faintest hints — the .tertiary replacement
    ground ground Warm paper — defined now, consumed by the panel ho
    groundDeep groundDeep A shade off ground — headers, chips (panel ho)
    panelGround panelGround The cooler "data" surface — the panel ho's reason this ho exists

    pālana's optional plugin/umber is dropped: Sharibako has no category axis (pālana §8 explicitly permits this). That keeps us at pālana's three working hues — paper, ink, moss — plus the single rust alarm voice.

    Decision 2 — pālana is light-only; the dark set is designed here (as siblings, portable upstream)

    pālana §2 gives only light values (warm paper ground, near-black ink). Sharibako must read well in both appearances (the AppAppearance override is live). So the dark half of every token is new , in pālana's spirit — warm throughout, never pure black or white, one moss accent, one rust alarm, lifted just enough to hold contrast on a dark ground.

    Token Light (pālana §2, authoritative) Dark (designed here)
    ground #FAF7F3 #1B1A17 (warm near-black)
    groundDeep #F4F1EA #24221E (raised warm)
    panelGround #EDEEF1 (cool slate) #20222A (cool dark slate)
    ink #1D1B18 #ECE7DF (warm off-white)
    inkSecondary ink @ 0.55α ink(dark) @ 0.60α
    inkTertiary ink @ 0.35α ink(dark) @ 0.40α
    accentMoss #5A7552 #7E9B72 (lifted moss)
    inSync (own tone) #5E8A50 #85BE73 (cleaner/brighter, gate-tuned)
    drift #984D3C #C56B57 (lifted rust)

    These dark values are the candidate to bring pālana in line later; they are written here so that a future pālana dark section can adopt them verbatim. pālana files are not touched in this ho.

    Decision 3 — Colorblind-safety is unchanged: stays primary

    inSync moss vs drift rust is a red/green axis — the exact thing the standing constraint guards. It holds because color stays secondary: the sidebar glyphs and drift badges keep their shape distinction (mappin.circle.fill vs arrow.up.forward.circle; checkmark.seal vs exclamationmark.triangle.fill) and their neutral inkSecondary fill. Only transient status pulses and text labels carry the moss/rust tint, and each of those also carries a word ("In sync" / "Differs") or a shape. No state is distinguishable by hue alone. The glyph/badge colors are therefore not migrated to moss/rust — they stay neutral, as ho-06.2 designed them.

    Decision 4 — Migrate text + accent + status now; keep the vibrancy materials

    Scope of the swap this ho performs:

    Mechanism

    Color+Theme.swift — a Color extension over an internal Theme/Palette layer. Each token is an appearance-aware color built with NSColor(name:dynamicProvider:): no asset catalog, no Package.swift resource change, and — because the raw values live in a pure Palette.resolved(dark:) seam — unit-testable without a running scene. The file carries real coverage and is therefore not added to the CI exclusion list.

    Tokens are named Color.accentMoss etc. and used explicitly (Color.drift, not .drift) because foregroundStyle(_: some ShapeStyle) won't resolve a leading-dot member against the generic parameter.


    Phase 2 — Build

    1. Sources/Sharibako/Color+Theme.swiftRGBA, Palette, Theme, and the Color extension exposing the nine tokens.
    2. Sources/Sharibako/App.swift.tint(Color.accentMoss) on every scene root.
    3. View migrations (Decision 4) across SecretList, SecretDetail, ScopeSidebar, WorkshopWindow, and the three Add sheets.
    4. Tests/SharibakoAppTests/ColorThemeTests.swift — assert every token's resolved light and dark RGBA against Decision 2, plus a dynamic-resolution test that exercises the appearance both ways.

    Verification each change: swift package clean && swift testswift build -Xswiftc -warnings-as-errorsswift-format lint --strict --recursive Sources Testsswiftlint lint --strict → coverage ≥90%.


    Phase 3 — Reflect

    As built (2026-07-11). The token layer shipped as planned. Color+Theme.swift holds the nine tokens over a pure Palette.resolved(dark:) seam and a dynamic NSColor(name:dynamicProvider:) provider — no asset catalog, no Package.swift change. Migrations landed across all seven views + App.swift (the .tint on every scene root). .borderedProminent CTAs picked up moss for free through the tint, so primaryActionButton() was untouched. The vibrancy materials (.bar, .quaternary) were left as-is (Decision 4); ground/groundDeep/ panelGround are defined and tested but not yet consumed — they are the panel ho's to spend. One explicit .primary (SecretDetail) was deliberately left on the system label to stay consistent with all the unstyled default body text; Color.ink is defined for the panel ho to consume alongside a real ground.

    Verification: 731 tests pass under -warnings-as-errors; both linters clean --strict; Color+Theme.swift at 100% line/region/function coverage; overall line coverage 94.78% (floor 90%). No CI exclusion added — the token file is fully tested, unlike the declarative View bodies.

    Visual gate — passed (2026-07-11). Unit tests prove the tokens resolve correctly but not that the palette reads well, so it was eyeballed on a real run in both appearances (selection/CTA moss, drift rust, status/error pulses, light and dark both holding as siblings with pālana). Andrew called it good.

    One gate-tuned change: the success/status pulse (moss @ 0.25 over the dark .bar) muddied to olive and didn't pop, so inSync was decoupled from the accent into its own cleaner/brighter moss (#5E8A50 / #85BE73) and both pulses were raised 0.25 → 0.40 (the accent on buttons/selection was left untouched — it read fine). Rust was confirmed scoped to drift, errors, and validation only — deliberately not the success pulse, since success and error must never share a hue. (Destructive-verb rust, which pālana also calls for, is not yet wired — Sharibako's delete affordances were outside this ho's migration; flagged for a later pass.)

    As-built gate note (correcting a wrong turn taken this session): the Workshop's real build and path is the committed xcode/Sharibako.xcodeproj — a -authored Xcode project over the same Swift package, signed Developer ID, producing Sharibako.app in /Applications ( §"Building from source"). That is how the GUI has been gated in every prior ho. During this session I wrongly reached for swift run and then a throwaway .app wrapper instead — both the wrong tool, both abandoned. The theme is reviewed by rebuilding the Xcode app. There is no owed signed-GUI install path; that path already exists — an earlier draft of this note claimed otherwise and was wrong.

    Forward — pālana upstream. The dark values (Decision 2) are the candidate to bring pālana in line (Andrew's note: the two must read as siblings, and this may recurse back into pālana). That is a pālana-side decision, tracked, not part of this ho — pālana files stayed untouched.

    State-summary

    Rendered from the corpus, verbatim · source on GitHub →

    ingested: sharibako @ 75a79d08a2b3 · ho-system @ 0f93b7fa32f7 · the glossary · the colophon