Skip to content

feat(style): animate + opacity + status lifecycle (pulse, ripple halo, done, muted) - #7

Merged
isConic merged 2 commits into
mainfrom
feat/style-animate
Aug 20, 2026
Merged

feat(style): animate + opacity + status lifecycle (pulse, ripple halo, done, muted)#7
isConic merged 2 commits into
mainfrom
feat/style-animate

Conversation

@isConic

@isConic isConic commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Style-schema animation extension: animate + opacity + status (+ ripple halo)

Adds attention-lifecycle styling to assets via the Style schema (no new verbs), so agents driving the map can visually indicate what's being processed:

New AssetStyle fields

  • animate — generic property animation, list of effects driven by one shared client rAF loop:
    [{"property": "opacity"|"circle_radius"|"stroke_width"|"ripple", "from": .., "to": .., "period": s}]
    • ripple is a sonar-ping halo: an auxiliary ring beneath point markers expands (fromto px) while fading to transparent (sawtooth), then restarts. Optional "color" tints it.
    • [] stops any running animation.
  • opacity — static 0..1 opacity (composes with hover highlight).
  • status — one-word lifecycle sugar, expanded server-side into concrete fields (explicit fields always win):
    • "active" → attention pulse (opacity + marker-size + ripple halo)
    • "done" → stop animation, full opacity, green success stroke
    • "muted" → stop animation, grayed out

glow is kept as back-compat sugar compiling to a single opacity effect.

Implementation

  • models.py: fields + _expand_status model validator; update_style broadcasts the expanded style so clients/session-restore only ever see concrete values
  • main.py (inline frontend): generalized animation engine replacing the glow loop; halo layer management for ripple; static-opacity application; restyle start/stop via update_style
  • SDK Style dataclass + MCP update_style docs mirrored
  • demo_status.py: 6-station drill-loop demo (active → done, muted pivot, raw ripple finale)

Tests

server/tests/test_style_animate.py — 10/10 model tests pass. The 3 async-fixture round-trip tests hit the pre-existing StreamableHTTPSessionManager.run() once-per-process limitation (same as tests/test_assets.py on main); not a regression.

…cycle presets

Adds three Style-schema fields (no new verbs, per the asset-style-ir
constraint) so agents can visually mark assets as being-processed /
completed / abandoned — e.g. survey stations in a pixel-drill demo:

- animate: generic property animation, a list of effects driven by ONE
  shared client rAF loop: [{property: opacity|circle_radius|stroke_width,
  from, to, period}]. glow stays as back-compat sugar that compiles to a
  single opacity effect; [] stops a running animation.
- opacity: flat 0..1 static opacity on fills/lines/circles, composing
  with the hover-highlight expression.
- status: one-word lifecycle sugar for LLM callers, expanded server-side
  into concrete fields (explicit fields always win):
    active -> attention pulse (opacity + marker-size)
    done   -> stop animation, full opacity, success stroke
    muted  -> stop animation, grayed out

Server: AssetStyle model validator expands status; update_style
broadcasts the EXPANDED style so clients only ever see concrete fields.
Frontend: glow engine generalized to the effects list; static opacity
paint; update_style cleanly starts/stops/re-tunes animations and resets
circle radius. SDK Style + update_style MCP tool docs mirrored.

Tests: tests/test_style_animate.py — status expansion, explicit-wins,
glow back-compat, animate round-trip, event round-trips, opacity-only
partial update. Demo: demo_status.py walks stations through
active -> done -> muted.

Note: the async-client fixtures share the pre-existing limitation that
StreamableHTTPSessionManager.run() is once-per-process (same pattern and
behavior as tests/test_assets.py on main).
…ssipates

Adds a "ripple" animate property: an auxiliary halo circle layer beneath
point markers whose ring expands (radius from→to px) while its opacity
ramps to transparent, then restarts — a sawtooth "dissipating wave"
rather than the existing sine oscillations. Optional "color" tints the
halo (defaults to the marker's fill color).

- models.py: document ripple in AssetStyle.animate; status="active"
  preset now expands to opacity + circle_radius + ripple (3 effects) —
  explicit animate still wins over the preset
- main.py (inline frontend): ensureHaloLayer/removeHaloLayer manage a
  'halo-{asset}' circle layer (reuses the asset source, inserted below
  the marker); animTick drives the sawtooth radius/opacity; registerAnim
  wires the halo in, unregisterAnim removes it; normalizeEffects passes
  the optional color through
- sdk/mapcontrol/models.py + mcp_tools.py: docs mirrored
- tests: +3 (active preset includes ripple w/ outward growth; custom
  ripple round-trips) — 10/10 model tests pass; the 3 async-fixture
  errors are the pre-existing StreamableHTTPSessionManager once-per-
  process limitation (same as tests/test_assets.py on main)
- demo_status.py: finale now shows a raw ripple spec (8→40px, amber)
@isConic
isConic merged commit fbfac2f into main Aug 20, 2026
2 checks passed
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