Skip to main content

Scaffolding chassis

A chassis-level UI help layer for Helm: every scaffoldable feature carries a tooltip that fades as the operator uses it and restores after long inactivity. The decay rate is multiplied by the operator's UI mode (beginner / intermediate / expert). Shipped in v0.6.41; pruned to a curated set in v0.6.42. The complementary discipline for which features earn a tooltip lives in tooltip protocol.

The model in one paragraph

Every staff member has a ui_mode of beginner | intermediate | expert. The mode multiplies the chassis's default decay thresholds (uses + days). Each scaffoldable surface tracks per-staff use_count and last_used_at. A tooltip shows when the operator hasn't yet used the feature enough times to clear the decay threshold, or when they have but it's been long enough since their last use that the chassis assumes they've forgotten. Expertise lives at the feature level, not the staff level — James can be an expert on the till and still see the consignment tooltip every time.

Migration 060

Five tables and four column additions on staff. The staff extension carries the per-operator UI mode; the four new tables carry the registry, per-operator state, per-shop overrides, suggestion queue, and sweep log.

TableGrainPurpose
staff (extended)one row per staffui_mode (text enum), ui_mode_set_at (ISO), ui_mode_set_by (default | user_explicit | heuristic_accepted), scaffolding_suggestions_enabled (0/1)
feature_registryone row per scaffoldable surfacefeature_key PK (e.g. sales.attach_customer), module_key, display_name, description (md OK), frequency_class (high/medium/low/rare), default_decay_uses, default_decay_days, soft-delete columns. Seeded idempotently on every boot via bootstrapFeatureRegistry.
staff_feature_statecomposite (staff_id, feature_key)use_count, first_used_at, last_used_at, tooltip_dismissed_at (operator clicked "got it"), tooltip_force_show (0/1, mutually exclusive with dismissed_at)
shop_tooltip_overridescomposite (shop_id, feature_key)Owner-only overrides of chassis defaults: override_decay_uses, override_decay_days, force_off, force_on (CHECK enforces mutual exclusivity). NULL on override_* = fall back to chassis registry default.
mode_suggestionsone row per pending suggestionWhen the daily heuristic decides a mode change is warranted: suggested_mode, reason, confidence (0–1), status (pending | accepted | dismissed | expired), resolved_by_action. Only one pending per staff at a time — the sweep checks before queueing.
scaffolding_sweep_logone row per daily sweep runran_at, evaluated_count, suggested_count, skipped_count, outcome. Self-gates the sweep on the existing 5-min cron — no new wrangler trigger needed; the sweep only runs if the last row is 24 h+ old.

Schema lineage: mirrors the design in KVICK.HUB's migration 005, adapted to Helm — users → existing staff table (no parallel identity), tenant_*shop_* (single-shop today; multi-shop forks would split on shop_id later), unix-seconds → ISO TEXT timestamps to match Helm's audit chain idiom, audit → recordMutation in src/index.js (not a separate writer).

Code module: src/scaffolding.js

Single self-contained ~900-LOC module. Pure-function decision engine + thin wrappers over the five tables. The chassis is route-handler-shaped — one dispatcher entry point + per-route helpers — so it can be wired into the existing src/index.js request handler without a framework migration.

Key exports:

FunctionPurpose
bootstrapFeatureRegistry(db)Idempotent seed; runs on first request. Soft-deletes orphans on every boot (features previously seeded but no longer in FEATURE_REGISTRY_SEED get is_deleted=1).
getEffectiveTooltipState(db, staffId, featureKey)The 7-rule decision engine — returns whether to show, hide, or force-show the tooltip for this staff + feature combination right now.
recordFeatureUse(db, staffId, featureKey)Increments use_count, sets last_used_at. Intentionally not auditeduse_count IS the record; the audit chain would be flooded.
dismissTooltip / forceShowTooltip / resetAllDismissalsPer-staff tooltip state manipulation. All audited via recordMutation.
getStaffMode / setStaffModeRead + write staff.ui_mode with set_by provenance.
Shop-override CRUDOwner / sys_admin gated.
computeStaffSignals / decideMode / evaluateModeForStaff / maybeRunDailySweepHeuristic pipeline — signals from staff_feature_state → mode recommendation → queue insert → cron-driven sweep. decideMode is pure for testability.
tryHandleScaffoldingRoute(request, env)Single dispatcher — returns a Response or null (let other handlers try).

API surface

Mounted in src/index.js. Every state change writes a mutation audit_event via recordMutation. recordFeatureUse is the deliberate exception — use_count IS the record; auditing every use would flood the chain.

GET /api/scaffolding/state/:featureKey -- {state, decay_remaining_uses, ...}
POST /api/scaffolding/use/:featureKey -- ++use_count, sets last_used_at
POST /api/scaffolding/dismiss/:featureKey -- operator clicked "got it"
POST /api/scaffolding/force-show/:featureKey -- "show me again"
POST /api/scaffolding/reset-dismissals -- nuke all dismissals for me

GET /api/staff/ui-mode -- {mode, set_at, set_by}
POST /api/staff/ui-mode -- {mode}
POST /api/staff/ui-mode/suggestions -- {enabled}
POST /api/staff/ui-mode/accept-suggestion
POST /api/staff/ui-mode/dismiss-suggestion -- {stop_suggesting?}

GET /api/admin/scaffolding-overrides -- owner/sys_admin only
PUT /api/admin/scaffolding-overrides/:featureKey
DELETE /api/admin/scaffolding-overrides/:featureKey

Mode → decay-multiplier mapping

The three modes don't change which features have tooltips; they change how fast the tooltip fades. The default decay numbers live in FREQUENCY_DEFAULTS keyed on feature_registry.frequency_class; each mode applies a multiplier:

ModeDecay multiplierImplication
beginnertooltips persist longest; threshold inflatedA fresh hire keeps the rails up on every feature for many uses
intermediate (default)chassis baselineMost operators most of the time
experttooltips fade fastest; threshold deflatedA tenured cashier sees a tooltip once or twice before it vanishes

Mode is per-staff, not per-shop, so a junior + a senior on the same till at the same time see different scaffolding.

The curated tooltip set (v0.6.42)

v0.6.41 seeded a broad-net 35-feature catalog of every potentially scaffoldable surface. v0.6.42 pruned it to 12 features that actually earn a tooltip:

sales.attach_customer sales.parked_resume
service.attach_customer service.generate_ticket
po.attach_supplier po.load_from_historic
po.open_po po.save_receive
po.delete_pending product.clone_sku
k_button.toggle settings.tax_rules

The rule applied (full statement in tooltip protocol): register a feature only if the UI alone — label, placeholder, position, surrounding context — doesn't teach the operator what the control does.

Pruned (23) included sales.add_line_item, sales.charge_customer, inventory.search, customer.create, rental.checkout, etc. — each had a button label or placeholder that already explained the control. Self-evident UI doesn't need tooltips.

bootstrapFeatureRegistry soft-deletes orphans on every boot: features that were seeded earlier but no longer appear in FEATURE_REGISTRY_SEED get is_deleted=1, so dropped features disappear from the UI on next cold start. The staff_feature_state rows stay for audit but don't surface.

Daily sweep cadence

The sweep that evaluates each staff member's signals + queues mode-change suggestions rides on the existing 5-minute cron (the content-publishing schedule). It self-gates via scaffolding_sweep_log: each tick checks the latest row's ran_at and only proceeds if it's 24 h+ old. The first successful run creates the seed row; subsequent runs UPDATE by id. No new wrangler trigger was added — Helm's wrangler config stays one cron.

What's deferred

  • UI wrapper components — the ScaffoldedTooltip wrapper, the mode picker, the suggestion banner — are deferred to a follow-up slice. The chassis is ready for any surface in public/index.html to call getEffectiveTooltipState + recordFeatureUse, but the visible tooltips themselves aren't wired up yet on most of the 12 curated features.
  • Scaffold any UI override beyond shop-level — a future slice could let operators tag specific tooltips as "never show me this again" without nuking all dismissals.

See also

  • Tooltip protocol — the rule for adding a new feature to the registry
  • Substrate Line — the chassis is substrate; the 12 curated features are surface
  • helm-editable doctrine — the in-situ edit chassis; scaffolding is its tooltip-mode cousin (one toggle vs. three states)