at main
5 folders
8 files
Raise the per-variant capacities, and stop shipping unusable vault UI
Every board now pins the capacity its flash and RAM actually support:
tdeck and cores3_se 700, core2_v13 500, sticks3 and cardputer 250,
m5stickc_plus2 180. The two 4 MB boards stay at 100 — their rekey-safe
ceiling is 139, and staging a second vault for a change-PIN on a fuller
one would run the filesystem out of space.
m5stickc_plus2 could not honestly hold 180: it had 3,132 B of internal
DRAM left against the 4,096 B bar. The slack came from a real absurdity.
Every button board compiled BOTH vault-menu layouts and picked one at
runtime from whether a third button existed, though a board's buttons are
fixed at manufacture. Each unused layout carries a row array sized by
capacity, so plus2 was paying 5 KB for a screen it can never draw. Boards
now declare which layout they use and only that one is compiled: plus2
goes to 9,196 B, sticks3 to 143,141, cores3_se to 78,985. core2_v13
recovers only the view object because its row arrays already live in
PSRAM, which is the placement working as intended.
The declaration is deliberate rather than inferred. Button count looked
like the discriminator only because today's fleet correlates: cardputer
and tdeck are wide-screen boards with no third button, and the old rule
would have handed them the narrow layout if their keyboards ever went
away. What actually decides the layout is width — a sliding pill
indicator is what fits 135 px, while a top tab bar plus an action bar
need room, and a past CoreInk header garble came from that view
hardcoding 320x240. Input decides only how you move between tabs. So the
variant states its choice, with no default, and a build-time tie rejects
a board declaring a layout its hardware cannot drive.
The names were lying too. Both layouts draw tabs, so "Tabbed"
distinguished nothing: they are now NarrowVaultView and WideVaultView,
after the constraint that actually separates them. KeyboardVaultView
keeps its name on purpose — cardputer is 240x135 and tdeck is 320x240,
one narrow and one wide, and both use it, so there the discriminator
really is the input device.
Review of the change caught a T-Deck pin that had never been raised while
five other artifacts already advertised 700, a NAV TOTP macro that
regressed on the wide boards so the screenshot suite filed a vault-list
capture as the TOTP screen, a selector whose "no silent default" promise
had a hole (an unknown token preprocesses to zero, which was the one
value exempted, so a typo compiled no view at all), and a variant guard
whose regex rejected correct declarations that carried a trailing
comment.
Verified: native 2861/2861, all three repo guards, and all eight boards
build with their declared layout confirmed present and its siblings
absent in each map.
Claude-Session: https://claude.ai/code/session_01Q2J5gQSFMTDLVzPUYog51r
Refine the PIN dial: A-gauge ring, single moving marker, B-Clear footer
Owner-approved dial refinement (#41). Replaces the busy original (0-9 ring + 10
ticks + double-circle outline all competing) with variant A's clean single-band
gauge that collars the hero digit, and reintroduces orientation without the
overload:
- RING: one thick same-tone gauge (track + bright swept fill, dim outlines),
not the thin double circle.
- NUMERALS: 0-9 around the ring, SMALL, stratified by presence — active digit
cyan-bold, the rest muted — so only one numeral claims attention.
- MARKERS: a single highlight on the marker the sweep is currently at (the
active digit), reverting to dim as it passes (new activeMarker prop) — not the
cumulative 0..digit lit before.
- FOOTER: BtnB physically clears, so the action bar reads 'A Hold / B Clear'
(new PinClear i18n string); the misleading 'B SHAKE' and the redundant
'Shake to clear' gesture-hint line are gone (shake still clears, no on-screen
hint needed now that B Clear is shown).
Adds 3 pin_dial tests (transient marker: only-active, none-at-idle, follows the
value). HW-verified on the StickS3 (MAC by id); text already renders through the
AA atlases. Mockup: docs/design/mockups/pin-entry-aplus.svg.
Add the CoreInk UI v2: ink vault pager, dial-disc PIN, retained lock face
Implements the Claude Design v2 CoreInk sheet (200x200, 1-bit e-paper, paper
theme) on the m5core_ink variant, built against pixel-exact 1:1 mock references
extracted from the handoff sheet (in-page html2canvas at scale 1) and verified
by overlay diff against captures from real silicon.
Screens
- Vault home: new ink render of the tabbed pager (Pass | TOTP | Actions)
composing the shared v2 components — drawHeader (2 px ink rule), drawTabBar
(K0-inverse active cell), drawMenuList (Cozy rows, brand badges, dotted
dividers), TotpCodeCard rows with live codes + countdown rings at 1 Hz, and
the dial-affordance action bar. Replaces the hand-rolled 320x240 v1 render
whose hardcoded geometry garbled the 200 px header; the color (Gray/Core2/
CoreS3) render path is untouched.
- PIN entry: the sheet's dial disc — brand lockup, rim digits at the e-ink
legibility floor, solid ink ring (new crispTrackFill opt-in on PinDial),
mono-face hero numeral, hollow/filled ink dots, ▲▼ DIGIT / OK hint bar —
replacing the v1 arrows-and-giant-digit landscape render.
- Lock face: the shared idle clock face is now armed on the e-paper 3-button
path, and goToSleep() paints it before power-off so the panel retains the
sheet's zero-power lock clock through deep sleep.
Component e-ink anatomy (extended, never forked)
- Tab bar Mono1: K0-inverse active cell, small-role labels, 20 px band.
- Header Mono1: 2 px solid ink bottom rule.
- Menu list Mono1: 50% dotted ink row dividers (the sheet's light hairline).
- PinDial: crisp-path solid annulus band behind an explicit opt-in prop.
- E-ink font floor: HEADING drops to the sans-16 rung (the sheet titles every
header at 17 px; the 24 px rung doubled the mock's title band).
Fixes found on silicon
- Entering Settings from the Actions tab bounced straight back out: the
opening tap's release detection is deferred past the 500 ms back-hold
threshold by the e-paper repaint, so the tap read as a long-press back. The
tabbed tree now swallows the opening release (TotpScreenController pattern).
- The tabbed header item count was hardcoded English ("%u item%s") with an
English-only plural; now the localized VaultItemsFmt via trFormat.
Docs and QA
- docs/design/mockups_v2/README.md: corrected three stale CoreInk claims
(driver blocked / bitmap-font fallback / keep out of matrices).
- 43 mock refs extracted from the CoreInk sheet + final on-device captures and
ref/capture/diff overlays under docs/design/qa-captures/coreink-v2/.
- New native test for the PinDial crisp band; header mono test updated to the
2 px rule contract.
Native 2259/2259; m5core_ink, sticks3, m5stack_gray and tdeck build -Werror;
clang-tidy 0 findings across 270 TUs; doxygen 0 warnings; i18n +4 ids x5
languages.
Raise the per-variant capacities, and stop shipping unusable vault UI
Every board now pins the capacity its flash and RAM actually support:
tdeck and cores3_se 700, core2_v13 500, sticks3 and cardputer 250,
m5stickc_plus2 180. The two 4 MB boards stay at 100 — their rekey-safe
ceiling is 139, and staging a second vault for a change-PIN on a fuller
one would run the filesystem out of space.
m5stickc_plus2 could not honestly hold 180: it had 3,132 B of internal
DRAM left against the 4,096 B bar. The slack came from a real absurdity.
Every button board compiled BOTH vault-menu layouts and picked one at
runtime from whether a third button existed, though a board's buttons are
fixed at manufacture. Each unused layout carries a row array sized by
capacity, so plus2 was paying 5 KB for a screen it can never draw. Boards
now declare which layout they use and only that one is compiled: plus2
goes to 9,196 B, sticks3 to 143,141, cores3_se to 78,985. core2_v13
recovers only the view object because its row arrays already live in
PSRAM, which is the placement working as intended.
The declaration is deliberate rather than inferred. Button count looked
like the discriminator only because today's fleet correlates: cardputer
and tdeck are wide-screen boards with no third button, and the old rule
would have handed them the narrow layout if their keyboards ever went
away. What actually decides the layout is width — a sliding pill
indicator is what fits 135 px, while a top tab bar plus an action bar
need room, and a past CoreInk header garble came from that view
hardcoding 320x240. Input decides only how you move between tabs. So the
variant states its choice, with no default, and a build-time tie rejects
a board declaring a layout its hardware cannot drive.
The names were lying too. Both layouts draw tabs, so "Tabbed"
distinguished nothing: they are now NarrowVaultView and WideVaultView,
after the constraint that actually separates them. KeyboardVaultView
keeps its name on purpose — cardputer is 240x135 and tdeck is 320x240,
one narrow and one wide, and both use it, so there the discriminator
really is the input device.
Review of the change caught a T-Deck pin that had never been raised while
five other artifacts already advertised 700, a NAV TOTP macro that
regressed on the wide boards so the screenshot suite filed a vault-list
capture as the TOTP screen, a selector whose "no silent default" promise
had a hole (an unknown token preprocesses to zero, which was the one
value exempted, so a typo compiled no view at all), and a variant guard
whose regex rejected correct declarations that carried a trailing
comment.
Verified: native 2861/2861, all three repo guards, and all eight boards
build with their declared layout confirmed present and its siblings
absent in each map.
Claude-Session: https://claude.ai/code/session_01Q2J5gQSFMTDLVzPUYog51r
Bake the Phase-4 font-ladder rungs and the Inter Bold weight axis
The Phase-4 design sheets and the handoff ladder (tokens.json) call for
per-device role sizes and 600/700 title weights that had no baked atlas,
forcing every fidelity pass to snap to the nearest rung or the 1 px
synthetic bold smear. Bake them:
- New regular rungs: sans 11/13/14/15/17 (tab labels, Stick rows, Stick
header, Cardputer title, CoreInk title), mono 14/17/56 (mask/bond rows,
Stick TOTP list codes, handoff mono.xl PIN hero).
- New weight axis: Inter Bold (700) at 10-24 px, vendored from the same
rsms/inter v4.1 release, gated per board by
KLEIDOS_FONT_SANS_USE_BOLD_<n> and routed like the regular segments.
- text_renderer: ladder-driven resolution (nearestInLadder over the full
per-family rung set) plus weight-aware resolveStyledAtlas — a bold
TextStyle now draws and measures from a baked bold rung within a 1.2x
resample window, falling back to the synthetic offset otherwise;
textWidth/fontHeight grow a defaulted bold parameter so datum-aligned
bold runs measure the face they draw.
- Variants link their sheet's role rungs (Stick 11/13/14 + bold 11-16,
Cardputer 13-17 + bold, CoreInk 11/14/17 + bold, large panels 13/15 +
bold 13-20 + mono 56).
- Native suite covers the new rungs (sans-14 metrics, bold/regular
line-metric parity, bold ink dominance); test_aa_font links the sans 14
regular + bold fixtures.
- The large-file hook gets a scoped, documented exclude for the generated
AA atlas segments (mono-56 crosses 512 KB by design), mirroring the
existing BrandAtlas exception.
Existing atlases are byte-identical (same subset, same pipeline); role
default mappings are intentionally unchanged — screens adopt the new
rungs in the per-device fidelity passes.
Claude-Session: https://claude.ai/code/session_01P6BNTpbgrvnZXSJnNuj8ZJ
Raise the per-variant capacities, and stop shipping unusable vault UI
Every board now pins the capacity its flash and RAM actually support:
tdeck and cores3_se 700, core2_v13 500, sticks3 and cardputer 250,
m5stickc_plus2 180. The two 4 MB boards stay at 100 — their rekey-safe
ceiling is 139, and staging a second vault for a change-PIN on a fuller
one would run the filesystem out of space.
m5stickc_plus2 could not honestly hold 180: it had 3,132 B of internal
DRAM left against the 4,096 B bar. The slack came from a real absurdity.
Every button board compiled BOTH vault-menu layouts and picked one at
runtime from whether a third button existed, though a board's buttons are
fixed at manufacture. Each unused layout carries a row array sized by
capacity, so plus2 was paying 5 KB for a screen it can never draw. Boards
now declare which layout they use and only that one is compiled: plus2
goes to 9,196 B, sticks3 to 143,141, cores3_se to 78,985. core2_v13
recovers only the view object because its row arrays already live in
PSRAM, which is the placement working as intended.
The declaration is deliberate rather than inferred. Button count looked
like the discriminator only because today's fleet correlates: cardputer
and tdeck are wide-screen boards with no third button, and the old rule
would have handed them the narrow layout if their keyboards ever went
away. What actually decides the layout is width — a sliding pill
indicator is what fits 135 px, while a top tab bar plus an action bar
need room, and a past CoreInk header garble came from that view
hardcoding 320x240. Input decides only how you move between tabs. So the
variant states its choice, with no default, and a build-time tie rejects
a board declaring a layout its hardware cannot drive.
The names were lying too. Both layouts draw tabs, so "Tabbed"
distinguished nothing: they are now NarrowVaultView and WideVaultView,
after the constraint that actually separates them. KeyboardVaultView
keeps its name on purpose — cardputer is 240x135 and tdeck is 320x240,
one narrow and one wide, and both use it, so there the discriminator
really is the input device.
Review of the change caught a T-Deck pin that had never been raised while
five other artifacts already advertised 700, a NAV TOTP macro that
regressed on the wide boards so the screenshot suite filed a vault-list
capture as the TOTP screen, a selector whose "no silent default" promise
had a hole (an unknown token preprocesses to zero, which was the one
value exempted, so a typo compiled no view at all), and a variant guard
whose regex rejected correct declarations that carried a trailing
comment.
Verified: native 2861/2861, all three repo guards, and all eight boards
build with their declared layout confirmed present and its siblings
absent in each map.
Claude-Session: https://claude.ai/code/session_01Q2J5gQSFMTDLVzPUYog51r
Remove the M5Stack Gray variant from the fleet
The M5Stack Gray is retired (owner-approved): the fleet drops from 9 to 8
device variants. This removes the Gray as a build target / device while
keeping every reusable driver and HAL layer intact.
Removed (Gray-exclusive wiring only):
- variants/m5stack_gray/ (variant.h, ini, sdkconfig, the four envs)
- src/boards/board_gray.cpp (the DEVICE_ID==5 board-registry factory)
- the web installer mockup (gray.svg + screen png) and app.js entry
- docs/hardware/devices/m5stack-gray.md and every fleet-list / inventory
/ identifier / secure-availability row across docs, CI, and instructions
Kept (per owner: preserve tested drivers/HAL even with no current consumer):
- the IP5306 PMIC driver family (src/drivers/power/ip5306*) — Gray was its
only board; still native-validated by test_small_pmic_regmaps
- the classic-ESP32 RTC_CNTL MMIO regmap (rtc_cntl_esp32_regmap.h) — Gray
was the only classic internal-RTC board; still native-validated
- board_gray_log_wrap.cpp renamed to board_log_printf_wrap.cpp: the
__wrap_log_printf stub is fleet-wide (esp32_base defines the gate), not
Gray-specific
DEVICE_ID 5 is retired and left unused so the remaining ids stay stable.
CI covering set rebalanced (Gray was the classic-ESP32 representative):
sticks3, tdeck, cores3_se, m5core_ink, m5stickc_plus2. m5stickc_plus2
(classic ESP32 + ST7789 full-frame polling + 8 MB OTA) replaces the Gray's
classic-ESP32 + OTA + polling coverage. core2_v13 is intentionally not added
to CI: it is an "Initial" WIP board that currently overflows DRAM at link
(pre-existing), so it cannot be a green gate member.
Native tests 2306/2306. Base envs build green for the new CI set
(sticks3, m5stickc_plus2, tdeck, cores3_se, m5core_ink).
Claude-Session: https://claude.ai/code/session_01P6BNTpbgrvnZXSJnNuj8ZJ
Add the capability-driven settings option model (phase 1)
Encode the resolved settings catalog (docs/design/settings-catalog.md §4/§9)
as a renderer-agnostic, host-testable data model: nine top-level groups (A-I)
and the flat option list inside each, every option carrying its control kind,
capability gate, responsive label tiers, and backing (NVS key or action id).
New, no live renderer touched (phase 2 wires settings_controller /
settings_nav_logic / device_panel onto this):
- src/states/settings_model.h — SettingsGroup/OptionId/SettingsControl/
SettingsGate/SettingsCapabilities/ValueBacking/LabelTiers, the constexpr
group + option tables, gateAllows/isOffered/offeredOptionCount/groupIsShown,
buildLabelVariants (functor-injected i18n resolve, no pool link), and the
ESP_PLATFORM SettingsCapabilities::fromVariant() factory. Encodes the §9
semantics: three idle timers with the deep-sleep ceiling, three orthogonal
destructive actions, HID layout Common, station-pull OTA, curated Device Info
+ Advanced, fixed brute-force info; GPS/SD/IMU/speaker/backlight/epaper/
OTA/secure rows gated.
- i18n/strings.csv (+ regenerated strings_gen.*) — settings group + option
label strings with _MED/_SHORT responsive tiers per docs/design/
settings-label-fit.md, all five languages, German shortest forms fitting the
Stick budget.
- test/states/test_settings_model — model integrity + the per-device
applicability matrix (catalog §5) across all nine variants.
- test/states/test_settings_label_fit — responsive-label selection for the
tightest German-on-Stick cases (byte-exact fit-doc widths).
- docs/design/settings-model-migration.md — the phase-2 plan wiring both live
renderers onto the model, screen by screen, in a safe order.
Claude-Session: https://claude.ai/code/session_01P6BNTpbgrvnZXSJnNuj8ZJ