Skip to content

API usability: resolve all audit blockers and friction (66 → ~88/100)#369

Open
HanSur94 wants to merge 12 commits into
mainfrom
api-usability-polish
Open

API usability: resolve all audit blockers and friction (66 → ~88/100)#369
HanSur94 wants to merge 12 commits into
mainfrom
api-usability-polish

Conversation

@HanSur94

@HanSur94 HanSur94 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

An empirical usability audit of the public FastSense API (/api-audit, live-measured in MATLAB R2025b) scored the baseline at 66/100: excellent core plotting ergonomics, but three cryptic-error traps on a new user's most natural first guesses, uneven input validation, and several discoverability gaps. This PR resolves every actionable finding — all additive, fully backward-compatible — and re-scores at ~88/100. Full per-finding history lives in docs/.api-usability/audit-2026-06-29.md.

Rebased onto main (advisory-review follow-up). A parallel API-usability workstream landed on main first (#212 + follow-ups): the widget-type list in unknownType, functionSignatures.json for FastSense/Dashboard, Tag-family unknownOption valid-key lists, and add* unknown-option warnings. Conflicts were resolved in main's favor where main was better; those items are main's work, not this PR's. An erratum in the audit report records the corrected credit.

🔴 Blockers — cryptic errors on natural first guesses

  • FastSense(x, y) (the plot(x,y) reflex) → namespaced FastSense:positionalData pointing at FastSense.plot(x, y) and the add-then-render builder (was raw MATLAB:mustBeFieldName)
  • addWidget(..., 'Tag', 'press_a') — a string key now works as a shortcut for TagRegistry.get(key); a missing key raises TagRegistry:unknownKey (was raw structRefFromNonStruct)
  • MonitorTag condition with wrong arity → MonitorTag:invalidConditionArity at construction (was raw TooManyInputs deep in getXY)

🟠 Friction

  • Added TagRegistry.keys() — sorted cellstr, empty-safe (documented but previously missing)
  • Namespaced validation at more entry points: DerivedTag compute arity, DashboardEngine Theme typos, positional addFill(x,y,baseline) now accepted, SensorTag:sizeMismatch, FastSense:invalidThreshold
  • Real help for DashboardEngine.addWidget / save / load (previously near-empty or leaking MATLAB's built-in save/load docs); documented all Thresholds forms and data-binding modes
  • functionSignatures.json for SensorThreshold (unique to this PR: all five Tag constructors + TagRegistry.get/register), plus XData/YData/Thresholds entries added to main's addWidget signature

🟢 Polish

  • SensorTag.fromStruct now documents that serialization omits X/Y data
  • De-jargoned MonitorTag help (Phase/Pitfall/MONITOR-NN codes → plain behavioral prose); fixed stale "FastPlot" name and "Plan 02" framing
  • Widget unknownOption suggestions no longer list internal h* graphics handles

Deferred (by design)

  • P4 — install boilerplate in 77 examples is the path bootstrap; not replaceable without .prj/toolbox packaging. Recommendation only, documented in the audit report.

Backward compatibility

No public signature, default, or serialization key changed. New validation only rejects input that already errored (raw) or silently misbehaved; every existing caller in the repo was verified unaffected (grep sweeps for theme names, condition/compute arities, positional data, Tag string bindings). The fromStruct load path resolves keys to objects before assignment and is untouched by the new set.Tag.

Test plan

  • New: tests/suite/TestApiUsabilityErrors.m — 19 regression tests pinning every new error ID and convenience behavior (positive + negative cases). All pass.
  • Post-rebase semantic verification: this PR's checks and main's parallel improvements coexist in the same functions (addThreshold numeric check + main's unknown-option warning; Tag ctor arity checks + main's valid-key lists) — probed live, all correct.
  • Suites green post-rebase: TestMonitorTag (28), TestSensorTag (20), TestDerivedTag (35), TestAddThreshold (7), TestDashboardEngineWidgetTypes (5), TestDashboardWidget (13), plus TestApiUsabilityErrors (19). Earlier pre-rebase runs additionally covered TestMonitorTagEvents, TestCompositeTag, TestTagRegistry, TestAddLine, TestFastSenseAddTag, TestFastSenseWidgetTag, TestDashboardMSerializer, TestDashboardEngine, TestGoldenIntegration — all green.
  • All three functionSignatures.json files pass validateFunctionSignaturesJSON; static analysis clean at every edit; end-to-end fresh-user task re-simulated successfully.

🤖 Generated with Claude Code

@HanSur94

HanSur94 commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

🤖 Automated advisory review — head afe8fa2a

Advisory only; no review state set. One comment per head-SHA.

🔴 Merge conflict with main — a parallel API-usability pass already landed there

mergeable: CONFLICTING. Main received overlapping work via #212 and follow-ups (02909675, 80f2b44c, fed93842, 8d7af2ce, 90d0579f, plus d606dd50, 853621ce). Four files conflict:

File Kind Recommended resolution
libs/FastSense/functionSignatures.json add/add Take main's — it's a superset (also covers addBand/addMarker/addShaded/setScale) and uses the proper FastSense.FastSense constructor key form.
libs/Dashboard/functionSignatures.json add/add Take main's key form; port over any NV entries ours has that main's addWidget lacks (Tag, XData/YData, Thresholds).
libs/Dashboard/DashboardEngine.m changed in both Keep main's unknownType message (02909675 — lists types and points at widgetTypes() for descriptions; strictly better than this PR's). Keep this PR's other edits (Theme validation, addWidget/save/load help).
docs/.api-usability/audit-2026-06-24.md add/add Two different sessions wrote different baselines under the same filename. Keep main's; fold this branch's baseline content into audit-2026-06-29.md or rename.

🟠 Findings

  1. Duplicated fixes / stale claims after rebase. F4 (widget-type list) and F8 for FastSense+Dashboard were fixed independently on main. After rebasing, drop those hunks and correct the audit reports + PR body so they don't claim credit for main's versions. libs/SensorThreshold/functionSignatures.json is genuinely unique to this PR — keep it.
  2. No new regression tests. The PR introduces 9 new error IDs/behaviors (FastSense:positionalData, MonitorTag:invalidConditionArity, DerivedTag:invalidCompute arity, DashboardEngine:unknownTheme, FastSense:invalidThreshold, SensorTag:sizeMismatch, set.Tag string resolution, TagRegistry.keys(), positional addFill baseline) with zero new test files. Existing suites pass (~200 runs verified during development), but nothing pins the new IDs/behaviors against regression. Suggest negative-case additions to TestMonitorTag, TestSensorTag, TestDerivedTag, TestDashboardWidget, TestTagRegistry, TestAddThreshold.

🟢 House-rule checks (pass)

  • Toolbox-free: pure MATLAB throughout ✓ · No MEX sources touched (parity N/A) ✓
  • Backward compat: no signature/default/serialization-key change; fromStruct load path resolves keys→objects before assignment, unaffected by the new set.Tag; repo-wide caller sweeps done for theme names, condition/compute arities, positional data ✓
  • Naming: the 5 added "FastPlot" strings are audit-report prose describing the stale-name finding; code changes actually remove FastPlot debt ✓
  • Hot path untouched; set.Tag adds a trivial ischar check per assignment ✓
  • CI: no checks reported on this PR yet at review time — nothing to classify.

Next step

Rebase onto origin/main, resolving the 4 conflicts per the table (favor main for functionSignatures ×2 and the unknownType message; keep everything else), update the audit reports/PR body to credit main's parallel fixes, then add the regression tests. After that this is a strong merge candidate — run /merge-ready 369 to confirm.

HanSur94 and others added 12 commits July 8, 2026 22:00
A MonitorTag built with a wrong-arity condition (e.g. @() or @(x)) used to
fail with a raw MATLAB:TooManyInputs deep inside getXY. The constructor now
checks the handle's arity up front and throws the namespaced, actionable
MonitorTag:invalidConditionArity, mirroring the existing invalidCondition guard.

Backward-compatible / additive only: no signature, default, or serialization
change. Only rejects already-broken 0/1-arg conditions (every condition handle
in the repo is 2-arg); valid @(x,y) and varargin handles are unaffected.
Proven: 108 Tag tests pass (MonitorTag 28, Events 14, Composite 31, Derived 35).

Also documents the new error ID in the class + constructor help, and marks
finding B3 resolved in docs/.api-usability/audit-2026-06-29.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Calling the constructor like plot(x, y) — a near-universal MATLAB reflex —
used to fail with a cryptic MATLAB:mustBeFieldName from the option parser.
The constructor now detects a non-string first argument up front and throws
the namespaced FastSense:positionalData, pointing at the existing one-liner
FastSense.plot(x, y) and the add-then-render builder.

Backward-compatible / additive only: the guard runs before option parsing and
only fires on a leading non-char/non-string arg — every valid call starts with
a string option name, and FastSense.plot forwards options (never data) to the
constructor. No signature, default, or serialization change.
Proven: 18 FastSense/integration tests pass (TestAddLine 8, TestFastSenseAddTag
9, TestGoldenIntegration 1); valid name-value calls, unknownOption, and
FastSense.plot(x,y) all verified unaffected.

Marks finding B1 resolved in docs/.api-usability/audit-2026-06-29.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
addWidget(..., 'Tag', 'press_a') — passing a registry key string instead of
the Tag object, a natural shortcut since tags are registered by key — used to
fail with a raw MATLAB:structRefFromNonStruct deep in the widget title cascade.
The DashboardWidget base class now has a set.Tag that resolves a char/string
via TagRegistry.get, so the string form works as a shortcut for
'Tag', TagRegistry.get('press_a'). A missing key raises the namespaced
TagRegistry:unknownKey ("...Use TagRegistry.list()...") instead of a raw crash.

Backward-compatible / additive only: Tag objects and [] pass through unchanged,
the 'Sensor' alias inherits the same convenience, and the fromStruct load path
(which already resolves keys to objects before assigning) is unaffected — no
signature, default, or serialization-key change.
Proven: 35 widget/serializer/engine tests pass (TestFastSenseWidgetTag 7,
TestDashboardMSerializer 10, TestDashboardEngine 18); static analysis clean;
string/object/Sensor-alias/missing-key paths all verified live.

Marks finding B2 resolved in docs/.api-usability/audit-2026-06-29.md; all three
red blockers (B1, B2, B3) are now closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TagRegistry documented keys as part of its public surface, but the method
did not exist — TagRegistry.keys() failed with a raw
MATLAB:subscripting:classHasNoPropertyOrMethod, and list() only prints (its
output cannot be captured). Callers reaching for the containers.Map.keys
reflex had no programmatic way to enumerate the catalog.

Adds a static keys() returning a sorted cellstr of all registered keys
(1x0 cell when empty) — the read counterpart to list(); find/findByKind/
findByLabel still return Tag objects. Purely additive (new method); no
existing signature or behavior changes.
Proven: 26 TagRegistry tests pass; empty/populated/sorted/iteration paths
verified live; static analysis clean at the new method.

Marks finding F1 resolved in docs/.api-usability/audit-2026-06-29.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y points

Six additive usability fixes, each turning a raw error or silent-accept into a
namespaced, actionable result. All only affect already-broken or already-
erroring input; every existing repo caller was verified unaffected.

- S2  DerivedTag: validate function-handle ComputeFn arity (@(parents)) up
      front -> DerivedTag:invalidCompute, instead of a raw MATLAB:minrhs in
      recompute_.
- V1  DashboardEngine: validate a string Theme preset at construction ->
      DashboardEngine:unknownTheme (parity with FastSenseTheme:unknownPreset),
      instead of silently falling back to 'light'. Legacy aliases
      (default/industrial/scientific/ocean) still accepted.
- V2  FastSense.addFill: accept a positional baseline addFill(x,y,baseline)
      as shorthand for addFill(x,y,'Baseline',baseline), instead of a raw
      MATLAB:badsubscript from the name-value parser.
- F4  DashboardEngine.addWidget: list all valid widget types in the
      DashboardEngine:unknownType message (parity with FastSense:unknownOption).
- F6a SensorTag: reject mismatched inline X/Y -> SensorTag:sizeMismatch.
- F6b FastSense.addThreshold: reject a non-numeric value ->
      FastSense:invalidThreshold.

Backward-compatible: no signature, default, or serialization change.
Proven: 85 tests pass (TestDerivedTag 35, TestSensorTag 20, TestAddThreshold 7,
TestDashboardEngineWidgetTypes 5, TestDashboardEngine 18 in isolation); the lone
testTimerContinuesAfterError flake is the known environmental timer test (passes
clean in isolation). Static analysis clean at all edits.

Marks S2, V1, V2, F4, F6a, F6b resolved in docs/.api-usability/audit-2026-06-29.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Help-text only (no behavior change):
- F2/F7 addWidget: full doc comment — the type form, the widget-object form,
  the valid types, and the FastSenseWidget data-binding modes (Tag /
  XData,YData / File,XVar,YVar / Thresholds). Was the misleading one-liner
  "Accept a pre-constructed widget object directly".
- F3 DashboardEngine.save / load: real doc comments (format from extension,
  tags serialized by key, register-before-load). They previously had no help,
  so `help DashboardEngine/save` showed MATLAB's built-in save() docs.
- S1 SensorTag.fromStruct: note that toStruct omits X/Y, so a round-tripped
  tag has no data until re-attached.
- P2 DerivedTag: stale "FastPlot" -> "FastSense" (post-rename).
- P3 CompositeTag: drop "implemented in Plan 02; Plan 01 ships core API only"
  framing — the merge-sort aggregation is implemented; describe current behavior.

Verified: help renders correctly for all; built-in save/load leak gone; no
"FastPlot"/"Plan 02" left in those headers.

Marks F2, F3, S1, P2, P3 resolved in docs/.api-usability/audit-2026-06-29.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The DashboardWidget:unknownOption error correctly lists valid options, but the
list mixed in internal graphics-handle properties (hPanel, hCellPanel,
hValueText, hUnitText, ...) that are never set via name-value pairs, burying the
real options. Filter out properties matching the h[A-Z] handle naming
convention from the suggestion.

Additive: error id and behavior unchanged; only the suggested-options string is
cleaner. Real options (StaticValue, Units, Title, Tag, ...) all retained.
Proven: 13 TestDashboardWidget tests pass; verified handles gone and real
options present in the message; no test depends on the old message content.

Marks P5 resolved in docs/.api-usability/audit-2026-06-29.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The public classes had no functionSignatures.json, so MATLAB offered no
argument/name-value tab-completion — a discoverability gap. Add validated
signature files for the three in-scope libraries:

- FastSense: constructor options + addLine + addThreshold
- Dashboard:  DashboardEngine constructor + addWidget (type choices, data binding)
- SensorThreshold: SensorTag / StateTag / MonitorTag / CompositeTag / DerivedTag
  constructors + TagRegistry.get/register

Purely additive (new files; no code change). All three pass
validateFunctionSignaturesJSON with no errors.

Marks F8 resolved in docs/.api-usability/audit-2026-06-29.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lp (P1)

F5 — the FastSenseWidget 'Thresholds' option already accepts a numeric
scalar/vector (upper limit lines) and a cell of {Value|X-Y, Direction, Label,
Color, LineStyle} structs (applyThresholds_ handles them); only 'auto'|false
was documented, so the capability was undiscoverable. Document all accepted
forms on the Thresholds property and in addWidget help. Verified live: numeric
[52 30] draws 2 lines, struct form draws a labeled line. (The original
"silent no-op" audit finding was a false positive.)

P1 — strip internal process codes (Phase 1006/1007/1010, MONITOR-NN,
Pitfall NN, Plan 02, Pass-2, EVENT-01) from the MonitorTag public help and a
dev-only "do not write TagKeys" note, rephrasing to plain behavioral prose.
All behavioral facts (lazy/in-memory, Persist opt-in, event-level callbacks,
ZOH alignment, callback timing, lifecycle) are preserved.

Help-text only (no behavior change). Proven: 28 TestMonitorTag tests pass;
MonitorTag parses clean; header verified jargon-free.

Marks F5 and P1 (MonitorTag) resolved in docs/.api-usability/audit-2026-06-29.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nale

Final live re-audit confirms every baseline raw-error/silent-accept probe now
returns a namespaced error or a working path. Update the 2026-06-29 report:
headline 66 -> ~88, record the full resolved set, and document P4 (example
install bootstrap) as deferred-by-design (it is the path bootstrap; not
replaceable by a helper without a .prj/toolbox packaging change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The canonical two-sensor + threshold + 2-widget-dashboard task that the 66
baseline could not complete via documented API now runs end-to-end (string Tag
keys, numeric widget thresholds, keys(), actionable ctor error). Logged as
confirming evidence for the ~88 re-score.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds tests/suite/TestApiUsabilityErrors.m (19 tests) pinning the new
namespaced error IDs and convenience behaviors from the api-usability batch
so they cannot regress to raw MATLAB errors: FastSense:positionalData,
MonitorTag:invalidConditionArity (+ varargin allowed),
DerivedTag:invalidCompute arity (+ @(parents) allowed),
DashboardEngine:unknownTheme (+ legacy aliases allowed),
FastSense:invalidThreshold, SensorTag:sizeMismatch, widget 'Tag' string-key
resolution (+ object passthrough + unknownKey), TagRegistry.keys()
(empty-safe, sorted), and addFill positional baseline (+ name-value form).
All 19 pass.

Also adds a post-rebase erratum to docs/.api-usability/audit-2026-06-29.md:
F4 and F8 (FastSense/Dashboard signatures) were independently fixed on main
first (PR #212 + follow-ups); this branch's unique F8 contribution is the
SensorThreshold signatures file plus the XData/YData/Thresholds addWidget
entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HanSur94 HanSur94 force-pushed the api-usability-polish branch from afe8fa2 to 1162eac Compare July 8, 2026 20:05
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.22222% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
libs/Dashboard/DashboardWidget.m 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant