Tooltip protocol
The scaffolding chassis can register every clickable surface in Helm — but most surfaces shouldn't be registered. This page is the protocol for deciding which ones earn a tooltip and which don't.
Established in v0.6.42 after the v0.6.41 seed registered 35 features broadly. After looking at the rendered tooltips against real UI, 23 of the 35 were pruned because their button labels and placeholders already explained the control. Self-evident UI doesn't need scaffolding.
The rule
Register a feature only if the UI alone — label, placeholder, position, surrounding context — doesn't teach the operator what the control does.
If a fresh operator can read the button text and figure out what'll happen on click, the tooltip is noise. If the visible affordance carries hidden behaviour, lives in shorthand, has costly misuse modes, or is genuinely discoverable-only, the tooltip earns its space.
The four checks
A feature qualifies if any one of these is true:
-
Side effect. Clicking the control does more than the label says. Example:
sales.parked_resumedoesn't just open a sale — it also releases the parked row's cart holds, swaps the till's source-tracking, and stamps an origin footnote. The label "Resume" doesn't teach any of that. -
Hidden affordance. The control isn't obvious from the surrounding chrome. Example:
k_button.toggle— the gear icon doesn't tell you it's the entry point to feature-by-feature commenting + edit-mode dispatch. -
Shorthand label covering multi-step behaviour. A one-word button hides a multi-stage flow. Example:
po.save_receive— "Save Receive" doesn't tell the operator the receive is held in draft state until that exact click, or that an all-zero save offers the short-shipment email path. -
Misuse costly. Clicking it on the wrong row, in the wrong mode, or at the wrong time has real cleanup cost. Example:
po.delete_pending— irreversible against an issued PO; the tooltip is the last warning before the confirm modal.
If none of the four apply, don't register the feature. A "Save" button labelled "Save" with helmConfirm on click is self-evident — no tooltip earned.
Worked examples
Kept (12 features in v0.6.42)
| Feature key | Why it earned a tooltip |
|---|---|
sales.attach_customer | Side effect: detach is the same pill in a different state; the tooltip teaches both interactions. |
sales.parked_resume | Side effect: clears parked row + restamps origin + releases holds. Label "Resume" undersells the contract. |
service.attach_customer | Same as Sales — also flips the ledger to customer history. |
service.generate_ticket | Multi-step: pill row → modal → service selection → parked-sale creation. Shorthand label. |
po.attach_supplier | Side effect: filters the PO ledger to that supplier and retitles the section header. |
po.load_from_historic | Shorthand: clicking a row "loads it" — but it also POSTs to a copy endpoint, stamps source_po_line_id, and switches the bucket into receive mode. |
po.open_po | Per-supplier preview-then-send; not obvious there's a preview step. |
po.save_receive | All-zero offers the short-shipment email path; held in draft until this click. |
po.delete_pending | Misuse costly; irreversible. |
product.clone_sku | Excludes stock + barcodes + supplier links + queue rows — non-obvious from the label. |
k_button.toggle | Hidden affordance: the gear is unfamiliar to a fresh operator. |
settings.tax_rules | Misuse costly: changes math across every future sale. |
Pruned (23 features from the v0.6.41 seed)
sales.add_line_item, sales.charge_customer, sales.special_order, sales.estimate, sales.refund, service.create_ticket, service.intake, service.complete, inventory.search, inventory.adjust, inventory.count, inventory.reorder_report, product.create, product.update_price, product.serialize, customer.create, customer.lookup, customer.merge_duplicates, rental.checkout, rental.return, rental.damage_log, consignment.intake/evaluate/payout, po.create, po.receive, po.receive_serialized, report.eod_z, report.margin, report.tax, settings.payment_processor, settings.team_member.
Each had a button label or placeholder that already explained the control. "Add to cart" + a product row is self-evident. "Charge $24.50" on a button is self-evident. Tooltips here would be wallpaper.
The seven-step process for adding a tooltip
- Identify the feature. Pick a stable
feature_keyinmodule.actionform:sales.attach_customer,po.delete_pending,product.clone_sku. Keys are forever — they're the primary key offeature_registry. - Apply the four checks. Side effect / hidden affordance / shorthand / misuse costly. If none apply, stop — the UI is self-evident.
- Pick a frequency class.
high(uses many times per day),medium(uses multiple times per week),low(uses a few times per month),rare(uses a few times per quarter). This drives the default decay numbers. - Write the
display_nameanddescription. Display name is the tooltip title — short, action-shaped ("Attach a customer to the till"). Description is the tooltip body — markdown allowed; should answer "what does this do that the label doesn't already say". - Add to
FEATURE_REGISTRY_SEEDinsrc/scaffolding.js.bootstrapFeatureRegistrywill upsert on next boot. - Wire
recordFeatureUseat the control's primary action handler. One call per genuine use of the feature. (Don't call it on hover, focus, or speculative mouse-down — only on the action firing.) - Wrap the rendered control in a
ScaffoldedTooltip(component coming in a follow-up slice; for now the call sites are stubbed). The wrapper readsgetEffectiveTooltipStateand renders accordingly.
What bootstrapFeatureRegistry does to retired features
Features that were seeded earlier but no longer appear in FEATURE_REGISTRY_SEED get soft-deleted (is_deleted=1) on every boot. They disappear from the UI on next cold start. The corresponding staff_feature_state rows stay around for audit but stop surfacing. Re-adding the feature key restores everything cleanly.
This is the mechanism v0.6.42 used to prune the 23 features without dropping their per-staff usage history.
See also
- Scaffolding chassis — the underlying decision engine + tables this protocol governs
- Substrate Line — tooltips are surface; the chassis is substrate
- Naming things — companion discipline for picking
feature_keys that won't drift