Skip to content

Timeline plugin: shell and component placeholders - #239

Open
sandesh-sp wants to merge 4 commits into
developmentfrom
feat/timeline-plugin-base
Open

Timeline plugin: shell and component placeholders#239
sandesh-sp wants to merge 4 commits into
developmentfrom
feat/timeline-plugin-base

Conversation

@sandesh-sp

@sandesh-sp sandesh-sp commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Base of the Timeline stack. Adds the plugin entry point, config, MMGIS API adapter, shared types and time utilities, the FloatingPopover primitive, and the adapter that owns timeline state and wires header, content and info popover together.

The five UI pieces ship as placeholders holding their prop contracts and layout slots — each is implemented in a follow-up PR in this stack.

Also adds the horizon theme, exports the primary-lighter/lightest colour tokens, and points the layer manager hover tint at the new token.

Stack — merge in order, starting from the base:

  1. Timeline plugin: shell and component placeholders #239 shell and component placeholders → development
  2. Timeline plugin: timeline view #240 timeline view
  3. Timeline plugin: date selector #241 date selector
  4. Timeline plugin: playback controls #242 playback controls
  5. Timeline plugin: time mode control #243 time mode control

Base of the Timeline stack: the plugin entry point, config, MMGIS API
adapter, shared types and time utilities, the FloatingPopover primitive,
and the adapter that owns timeline state and wires the header, content
and info popover together.

The five UI pieces — timeline view, date selector, playback controls,
playback speed and time mode — ship as placeholders that hold their prop
contracts and layout slots. Each is implemented in its own follow-up PR
in this stack.

Also adds the horizon theme, exports the primary-lighter/lightest colour
tokens, and points the layer manager hover tint at the new token.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgqjkZhQvfoBgdJtt9SSKS
sandesh-sp and others added 3 commits July 31, 2026 10:23
The horizon brand's tokens and theme entry point, along with its option
in the configure page's UI tab, live on feat/horizon-theme and land in
their own PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgqjkZhQvfoBgdJtt9SSKS
Core integration:
- Subscribe to 'time:changed' (the event TimeControl actually broadcasts) and
  ignore this plugin's own committed echo, so external time changes reach the
  timeline and a stale window is never pushed back to core.
- Gate 'tool:getVars' and the layer reads on useMMGISHandlerReady, matching the
  sibling modern tools; these handlers are registered in Layers_.fina() after
  the tool mounts, so the previous mount-time request always rejected.
- Read layer configs and visibility over the request/provide bus
  ('layers:getAllConfigs', 'layers:getVisible') instead of direct
  window.mmgisAPI methods, and drop the Timeline-local type widening.
- Add a 'time:isEnabled' provider to TimeControl and render an explicit
  unavailable state, replacing the fabricated now-30d/now-15d window that
  rendered a fully interactive timeline core silently discarded.

Playback and stepping:
- Keep the setCurrentTime updater pure; the emit and the stop now run in the
  interval body against refs, which also stops the interval re-arming per tick.
- Clamp step targets to the range instead of dropping the step, expose
  canStepForward/canStepBackward, and restart from the start when play is
  pressed at the end of the range.

Accessibility and styling:
- Restore a focus-visible ring for every control; core's global
  `*:focus { outline: none }` removes the native one.
- Give FloatingPopover dialog semantics, Escape-to-close and focus restore, and
  wire aria-expanded/aria-controls on the triggers.
- Scope `.ui-tool-card { width: 100% }` to vertical and tabbed regions so a
  top/bottom panel holding more than one tool still lays them out in a row.
- Export --theme-color-primary-lightest for every brand, falling back to
  primary-lighter, so the three LayerManager hover rules resolve per-brand
  instead of inheriting the disasters teal literal.
- Set the info popover's font; it portals outside the .timeline subtree.

Also: keep generateTimeTicks inside its domain and terminate on degenerate
ranges, unmount a prior root before make() creates a new one, drop the eight
debug console.log calls and the never-populated _cleanups array, and add unit
tests for the tick and clamp helpers.
@sandesh-sp

Copy link
Copy Markdown
Collaborator Author

Review pass over the stack (#239#243) surfaced 28 issues; 20 of them land in this PR, since the adapter, the tool shell and every out-of-plugin edit live here. Pushed as fbea792.

Blockers

Dead time subscription. The adapter listened on time:change. Nothing emits that — core broadcasts time:changed (TimeControl.js:819). So an external time change never reached the timeline, and the next scrubber drag emitted time:changeRequested carrying the stale start/end, reverting the global window and refetching every time-enabled layer for the wrong range. Now on time:changed, with the plugin's own committed echo matched and skipped so it can't fight an in-flight drag or playback tick. Times are compared by instant, so an echo carrying an equal Date no longer re-arms effects keyed on it.

Config was always discarded. tool:getVars was requested once at mount, but core registers it in L_.fina() after all layers load, and mmgisAPI.request throws for an unregistered name — caught into a console.warn, [] deps, no retry. Every Configure setting (allowPlayback, shownTimeModes, defaultTimeMode, and the tool's width/height) was silently dropped. Now gated on useMMGISHandlerReady, matching Card, Title, LayerManager and ShareExport; Timeline was the only modern plugin missing the guard.

Also fixed here

  • No time-disabled path. TimeControl.init() registers its getters before the enablement check and returns early, so on a mission without time the getters resolve null, the if (start && end && current) guard fell through, and the constructor's now-30d / now-15d seeds survived — a fully interactive timeline over invented dates whose every emit core drops behind if (TimeControl.enabled). Added a time:isEnabled provider next to the existing time:get* ones and an explicit unavailable state.
  • Layer reads moved onto the bus. layers:getAllConfigs / layers:getVisible replace the direct window.mmgisAPI methods; the Timeline-local type widening and the MMGISAPI export it required are gone. The comment justifying the escape hatch was wrong — those bus handlers exist and LayerManager already uses them.
  • .ui-tool-card { width: 100% } was on the unscoped base rule, but top/bottom regions are flex-direction: row with flex: 0 0 auto cards, so a horizontal panel holding two tools totalled 200% and pushed the second off-screen. Scoped to vertical and tabbed regions, plus an :only-child case for a lone horizontal tool.
  • --theme-color-primary-lightest was exported only where a brand set it — disasters alone — so the three LayerManager hover rules this PR moved onto it resolved to the hardcoded disasters teal on default and earthgov. Now exported unconditionally, falling back to each brand's own primary-lighter. Verified in the compiled output: default/earthgov #d9e8f6, disasters #e6f4f6.
  • Playback correctness. setCurrentTime's updater called mmgisEmit and setIsPlaying from inside itself; both now run in the interval body against refs, which also stops the interval tearing down and rebuilding on every tick. Steps clamp to the range instead of silently doing nothing near an endpoint, and pressing play at the end restarts from the beginning.
  • Focus ring restored for every control — core's global *:focus { outline: none } (mmgis.css:754) strips the native one and nothing here replaced it.
  • FloatingPopover got role="dialog", Escape-to-close, opt-in focus move and focus restore; triggers carry aria-expanded / aria-controls.
  • Info popover sets its own font (it portals outside .timeline and was inheriting core's Roboto); generateTimeTicks keeps every tick inside its domain and terminates on degenerate ranges; make() unmounts a prior root first; the eight debug console.logs and the never-populated _cleanups array are gone.

Added tests/unit/timelineTimeUtils.spec.js covering the tick and clamp helpers — the plugin had no test coverage at all.

tsc --noEmit clean, 898 unit tests pass, build:themes succeeds for all three brands.

Not fixed

The collapse button still leaves the host panel at full height. The blank area is core's .ui-panel-content with a JS-set inline height (UserInterfaceModern_.js:617), so no plugin-local change reaches it — a real fix needs new panel API on the bus, which felt like the wrong thing to add from a plugin PR. The misleading comment claiming the tool shrinks to fit has been corrected; happy to wire it properly if you'd rather.

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