Annotation: HED short form with long-form hover - #268
Merged
neuromechanist merged 1 commit intoSep 1, 2026
Conversation
Yahya's QA round 4: - New pure hedShortForm(): leaf tag with the sc: library prefix carried over. formatHed() now writes short form to events.tsv/channels TSV (HED's annotation default; validators expand via HEDVersion). - Panel rows and chips show short form even before the lazy vocab loads (labelForPath falls back to hedShortForm, not the raw path — how long paths leaked into the UI); the long form moves to hover titles on rows and chosen chips. - Annotation list becomes a responsive grid: 2-3 columns of short rows on a wide panel, collapsing to one, row-major. Tested: typecheck, lint, 1350 vitest (serializer expectations updated to short form; new hedShortForm cases).
Deploying nemar-website with
|
| Latest commit: |
5eaf35d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://d918edb6.nemar-website.pages.dev |
| Branch Preview URL: | https://fix-hed-short-form.nemar-website.pages.dev |
neuromechanist
merged commit Sep 1, 2026
543a103
into
feature/issue-256-epic-viewer-workbench
5 checks passed
This was referenced Sep 1, 2026
neuromechanist
added a commit
that referenced
this pull request
Sep 1, 2026
* Surface a degraded view pyramid in the viewer GroupHandle.viewLevelsDegraded was computed but never read, so a truncated overview pyramid looked identical to a short recording. The status line now carries a suffix and a standing note sits under the minimap; both re-check when viewLevelsReady settles after first paint. * Push annotation persistence failures to the viewer UI mutate() runs syncAll() synchronously before the debounced flush, so a write that fails on the last annotation before a tab close never re-armed the beforeunload guard nor repainted the "not being saved" banner. The store now notifies subscribers from degrade(); the annotation layer subscribes at open and re-runs syncBeforeUnload() + renderPanel(). * Stop the preloader retrying into an outage PrefetchController.loop walked the whole recording re-failing forever with only a static buffered bar as feedback. Four consecutive segment failures (each already past store.ts's retry ladder) now stop the walk and fire onStalled; the viewer shows a note beside the preload toggle, and any restart -- toggle, cache cap, group switch -- re-arms it. Aborts do not count as failures. * Block recording swaps while an annotation draft is open isPopoverOpen() had zero callers, so prev/next and the subject/task dropdowns destroyed the mounted instance and its unsaved popover draft silently. The viewer now hands the page a ViewerAnnotationHandle on the same seam as onTransfer; navigateEegViewer, stepEegViewer and both select handlers refuse and flash+focus the popover instead. No window.confirm -- the refusal plus a visible cue says the same thing without a blocking modal. * Drop a manual gain across a modality change applyTransfer carried a manually-set gain onto any new recording. Gain multiplies the modality's DEFAULT_SCALINGS, so an EEG gain on an MEG recording is a different physical scale, not the same zoom. The transfer record now carries the modality it was chosen against; a mismatch falls back to auto-scale. Same-modality swaps are unchanged. * Tear down a mid-flight viewer on dialog close The close handler relied on eegLive.destroy, which navigateEegViewer nulls for the duration of the new mount. Fall back to the host's own _eegvCleanup so an instance that published it but has not returned its disposer yet is released immediately instead of at the post-await staleness check. * Surface an annotation export that fails to download The export is the escape hatch for annotations that live nowhere else, so a throwing createObjectURL/click must not read as an inert button. Wrap it and render the failure beside the export buttons; a later success clears the message. * Warn when the nav-order preference cannot be stored writeNavOrder returns false for exactly this (privacy mode, blocked storage) and the return value was discarded. * Fix stale comments and docs from the release panel - hed-vocab.ts / annotation-ui.ts: the bundle is ~341 KB and 1525 entries, not ~140 KB / 500, and it is no longer curated (website#269). - HedPath / HedVocabEntry.path / extract-hed-vocab.mjs: the long form is the storage and lookup key; the SHORT form is what an export writes (#268). - annotation-ui.ts labelForPath: it falls back to the derived short form, matching its own inline comment. - render-dir-listing.ts: the missing half of the CHEVRON_SVG sync comment. - ADR 0013: dated update note; search now covers both schemas in full, curation narrowed to quick picks, decision unchanged. - AGENTS.md: real test count, and eeg-viewer/ now names what it holds. - [id].astro: releaseEegViewer has two call sites, and openRecordingInDialog can be reached with an inline viewer live. - extract-hed-vocab.mjs: drop the unused byPath map (regenerating the bundle after the change produces a byte-identical file). * Let a dialog refuse its own corner X DialogCloseButton calls dialog.close(), which fires only the non-cancelable "close" event -- a decision already taken. So the X walked straight past the annotation-draft guard that Escape respects via "cancel", and the detached-close branch destroyed the draft with no trace. The button now dispatches a cancelable pre-close event; the dataset page vetoes it while a detached viewer holds an open popover. Inert for every dialog that does not listen. * Derive the preload stall note instead of latching it The note advertised "turn it off and on again to retry", and that cure provably failed: renderImpl's read-failure branch returns before updatePrefetchTarget, and the toggle-on path early-returns while no view level is known, so the latched flag survived its own recovery and the note stuck for good. The outage that trips the breaker is the same one that fails the interactive read, so this was the common case. The note is now derived from PrefetchController.stalled, which stop() clears alongside start(), and is re-evaluated on every render exactly as the degraded-pyramid note already was -- that asymmetry was why one self-healed and the other did not. * Make the viewer's destroy() idempotent Three handles point at the one disposer, and a dialog close during a navigate mount fires two of them: the close handler takes host._eegvCleanup, then navigateEegViewer's superseded branch calls the same returned destroy. Running the cleanups twice double-disposes the GL context and tears the annotation layer down mid-flush. Guard in the closure, which is the only place that can see all three call sites. * Fail open when the annotation draft guard throws A throwing isPopoverOpen/focusPopover would otherwise wedge every prev/next and both dropdowns for the rest of the session, with the selects re-syncing away from the user's pick and no gesture that recovers. Losing at most one unsaved draft is the smaller failure. * Extract and test the modality gain-transfer gate gainCarriesOver was an inline expression in applyTransfer's closure. Its failure mode is a trace off by orders of magnitude -- a flat line or a wall of clipping that reads as the recording, not as a bug -- so it does not belong untested. Pure, exported, and covered for same/different modality, case, whitespace, and the null-safety convention that an absent modality counts as a match. * Cover the load-first annotation persistence degrade The realistic first contact is mount, subscribe, load -- with no prior save to have caught an unusable connection. That is the visit where the annotator would be told their marks are safe when they are not. * Stop the degraded-overview note naming a cause ViewLevelDiscoveryError covers a retry-exhausted 5xx, a 403 from an expired token and a decode error alike, and the flag does not say which, so "connection problem" was a guess presented as a diagnosis. The action is the same either way: reload. * Codify the ADR amendment convention ADR 0013 grew an "## Update -- YYYY-MM-DD" section for a factual change that left the Decision standing. Write the rule down: amend by appending, never by editing Context/Decision/Alternatives, and supersede when the Decision itself changes. * Fix a stale caller claim and the test count releaseEegViewer's inline comment still named openEegViewerInline as its only caller, contradicting the header six lines above it that now names both. AGENTS.md's test count follows the suite to 1402.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Live-QA round 4 from Yahya (part of epic #256):
Gates: typecheck 0 errors, lint clean, 1350 tests (serializer expectations moved to short form, new hedShortForm cases).