Skip to main content

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:

  1. Side effect. Clicking the control does more than the label says. Example: sales.parked_resume doesn'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.

  2. 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.

  3. 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.

  4. 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 keyWhy it earned a tooltip
sales.attach_customerSide effect: detach is the same pill in a different state; the tooltip teaches both interactions.
sales.parked_resumeSide effect: clears parked row + restamps origin + releases holds. Label "Resume" undersells the contract.
service.attach_customerSame as Sales — also flips the ledger to customer history.
service.generate_ticketMulti-step: pill row → modal → service selection → parked-sale creation. Shorthand label.
po.attach_supplierSide effect: filters the PO ledger to that supplier and retitles the section header.
po.load_from_historicShorthand: 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_poPer-supplier preview-then-send; not obvious there's a preview step.
po.save_receiveAll-zero offers the short-shipment email path; held in draft until this click.
po.delete_pendingMisuse costly; irreversible.
product.clone_skuExcludes stock + barcodes + supplier links + queue rows — non-obvious from the label.
k_button.toggleHidden affordance: the gear is unfamiliar to a fresh operator.
settings.tax_rulesMisuse 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

  1. Identify the feature. Pick a stable feature_key in module.action form: sales.attach_customer, po.delete_pending, product.clone_sku. Keys are forever — they're the primary key of feature_registry.
  2. Apply the four checks. Side effect / hidden affordance / shorthand / misuse costly. If none apply, stop — the UI is self-evident.
  3. 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.
  4. Write the display_name and description. 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".
  5. Add to FEATURE_REGISTRY_SEED in src/scaffolding.js. bootstrapFeatureRegistry will upsert on next boot.
  6. Wire recordFeatureUse at 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.)
  7. Wrap the rendered control in a ScaffoldedTooltip (component coming in a follow-up slice; for now the call sites are stubbed). The wrapper reads getEffectiveTooltipState and 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