Skip to content

Desktop viewport layout, plus a dead-code and duplication pass - #6

Open
ameesme wants to merge 3 commits into
mainfrom
claude/ponytail-lazy-mode-mul5sw
Open

Desktop viewport layout, plus a dead-code and duplication pass#6
ameesme wants to merge 3 commits into
mainfrom
claude/ponytail-lazy-mode-mul5sw

Conversation

@ameesme

@ameesme ameesme commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Two commits: a cleanup pass with no behaviour change, then a desktop layout change on top. Version 2.3.0.


1. Fill the viewport on desktop, scroll each column internally (cbec0e8)

The desktop panel grew the page — a tall timeline pushed everything down and the schema header scrolled out of reach. It now behaves like the small-screen layout: the panel is exactly as tall as the viewport, the header stays put, and the timeline and side sheet each scroll in their own column.

  • The host is a viewport-height flex column with overflow: hidden, so the page itself never scrolls. Measured at 1400×900 and 1280×620: page overflow 0px in both; at 620px the timeline pane has 28px of internal scroll and the sheet 122px.
  • The timeline card fills its column with the rows scrolling inside it, and the hour header is sticky — without it the 00–23 labels scroll away and the chart can't be read. Mobile already did this; the rule moved to the shared base with --surface on desktop (mobile keeps --bg, its card being flattened).
  • The side sheet keeps its title, subtitle and close button in place and scrolls only the form under them, via a new .side-body wrapper — the same split the mobile drawer already used. Scrolling the whole sheet drags the ✕ off-screen.
  • Most of this already existed in the small-screen media query, so it moved to the shared base; mobile keeps only its deltas (fixed host, edge-to-edge padding, flattened-card header colour).

2. Cut dead code and duplication; drop the JS engine port (f435cec)

No user-facing behaviour changes. Net −460 lines of source.

Dev harness: frontend/dev/engine.ts is gone

It was a 250-line second implementation of the lighting math, kept in sync with engine.py by hand — and an approximate one (sunrise/sunset faked at fixed clock hours, fractional-hour space instead of datetimes), so it could never actually catch a divergence. tests/test_engine.py is what does that.

mock-backend.ts now serves a SUN_ROW fixture captured from the real engine.py under the default sun config, with each light's explicit hour cells laid over it. The timeline, cell editor, overrides, RGB swatches, colourless rows, selection and status all still work in npm run dev; the one thing lost is that editing the sun config no longer moves the curve in the harness. CLAUDE.md updated to say so, including how to regenerate the fixture.

Backend

  • Dead status payload. sundial/status shipped ~23 fields nothing renders: per-light manual_since, last_applied, last_applied_at, settling, supports, supported_color_modes, config; the sun's position, drive, coordinates and *_source; eight globals that echo settings the panel already has; local_hour. Deleted, along with the LightRuntime fields and coordinator accessors (is_settling, last_pass_at, supports_rgb) that existed only to feed them.
  • WS readiness guard. The same three lines were repeated in all twelve handlers. One @_with_coordinator decorator, same error code and message.
  • Accessor pairs. _supported_modes/supported_modes and _compute_target/compute_target were private methods plus public passthroughs. One public method each.
  • Context tracking. A deque plus a parallel set to bound our own service-call context ids → one insertion-ordered dict, same bound, same O(1).
  • The tanh ramp is only ever fitted through (0, 0.05) and (1, 0.95), so find_a_b solved the same two coefficients on every call. Baked in at full double precision — verified bit-identical across the domain.
  • Three copies of round-to-nearest-5 → engine.round5; five hand-written to_dict bodies → dataclasses.asdict; the identical device_info in select.py and switch.py → one shared helper.
  • Fixed a docstring still describing the group-narrowing behaviour that was reverted in Revert narrowing group writes to lit members (fixes 2.2.1 regression) #5.

Panel

  • The row-preview strip duplicated the timeline's cell markup, colour rule and ~45 lines of CSS. Both now share cellStyles and cellColor(); each keeps its own cell height, so the drawer strip stays 42px and the grid keeps its 52px mobile rows.
  • Status types trimmed to the fields actually read; four inline config.lights.find(...) calls now use the existing _lightInfo(); dropped a .grow rule already in baseStyles and the byte-identical label.field/div.field blocks; _load() was _run(getConfig()) written out.

Deliberately not done

  • _apply_external — flagged as speculative, but removing it would actually change behaviour: sundial.apply on a light Sundial doesn't control currently applies the sun default to it. Left alone.
  • The target branch in interceptor.py — likely dead (HA merges target into service_data before firing EVENT_CALL_SERVICE), but I couldn't verify against a real instance, and being wrong means explicit-colour calls stop flagging manual control. Left alone.
  • Native <dialog> close animation (@starting-style / allow-discrete) — doesn't work here: the drawer is conditionally rendered, so @close_sel = null removes the node before any CSS transition can run. The existing 30 lines stay.

Verification

ruff check, pytest (33 passed), tsc --noEmit and vite build all clean; bundle regenerated and committed.

Drove the dev harness in Chromium, no console errors in any layout:

  • Desktop (1400×900 and 1280×620): both cards fill the viewport, page overflow 0px, each pane scrolls internally; scrolled both to the bottom to confirm the hour header sticks and the sheet's title and ✕ stay pinned.
  • Mobile (iPhone 13, 390×844), re-run after the layout refactor: sticky icon header, stacked rows under sticky hour labels, bottom-drawer sheets for a light and an hour cell, drawer close returning to the grid. Cell heights measured — grid 52px, preview strip 42px, unchanged.
  • Both paths that use what's left of the status payload: the group light (Group — 3 lights · 1 on plus the group warning) and the sun sheet (Adaptation, Sun — night · up 08:23 · down 17:36, Sun now, Next run, Lights — 7 controlled · 1 manual).

🤖 Generated with Claude Code

https://claude.ai/code/session_012UavT5bm5ZN2f1tNd7Yz1H

claude added 2 commits August 7, 2026 17:55
An over-engineering pass across the integration and the panel. No
user-facing behaviour changes.

Backend:
- Drop the status payload fields nothing renders (per-light manual_since,
  last_applied*, settling, supports, supported_color_modes, config; the
  sun's position/drive/coordinates/sources; eight global settings echoes;
  local_hour), plus the runtime state and coordinator accessors that
  existed only to feed them.
- Replace the twelve repetitions of the "is the integration ready" guard
  in the WebSocket handlers with one decorator.
- Fold the private/public accessor pairs on the coordinator into single
  public methods, and drop the unused supports_rgb.
- Track our own service-call contexts in one bounded insertion-ordered
  map instead of a deque plus a parallel set.
- The tanh ramp is only ever fitted through (0, 0.05) and (1, 0.95), so
  bake its two coefficients in rather than solving per call. Values are
  bit-identical.
- One round-to-5 helper instead of three copies; dataclasses.asdict
  instead of five hand-written to_dict bodies; one device_info shared by
  the select and switch platforms.

Panel:
- Share the 24-cell chart styling and fill-colour rule between the
  timeline rows and the row-preview strip.
- Trim the status types to the fields the panel reads, use the existing
  _lightInfo lookup, drop a duplicated .grow rule and the identical
  label.field/div.field blocks, and fold _load into _run.

Dev harness:
- Delete frontend/dev/engine.ts. It was a second implementation of the
  lighting math kept in sync by hand, and an approximate one (fixed
  sunrise/sunset), so it never verified anything. The mock backend now
  serves a sun-row fixture captured from engine.py with explicit hour
  cells laid over it: the timeline, cell editing, overrides, RGB and
  status all still work; editing the sun config no longer moves the
  curve in the harness.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UavT5bm5ZN2f1tNd7Yz1H
The desktop panel grew the page: a tall timeline pushed the whole thing
down and the schema header scrolled out of reach. It now behaves like
the small-screen layout — the panel is exactly as tall as the viewport,
the header stays put, and the timeline and side sheet each scroll in
their own column.

- The host is a viewport-height flex column with overflow hidden, so the
  page itself never scrolls; the layout grid takes the remaining height
  and both columns get min-height: 0 to scroll rather than stretch.
- The timeline card fills its column with the rows scrolling inside it,
  and the hour header is sticky — otherwise the 00-23 labels scroll away
  and the chart can't be read.
- The side sheet keeps its title, subtitle and close button in place and
  scrolls only the form under them, in a new .side-body wrapper. This is
  the split the mobile drawer already used; scrolling the whole sheet
  would drag the close button off-screen.
- Most of this was already in the small-screen media query, so it moves
  to the shared base and mobile keeps only its own deltas: the fixed
  host, edge-to-edge padding, and the flattened card's header colour.

Version 2.3.0 rather than a patch: this changes how the panel behaves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UavT5bm5ZN2f1tNd7Yz1H
@ameesme ameesme changed the title Cut dead code and duplication; drop the JS engine port Desktop viewport layout, plus a dead-code and duplication pass Aug 7, 2026
The timeline's drag row sat above the grid at all times, offering a
control that does nothing unless preview is on. Gate it on previewActive
like the small-screen scrub bar already is, so both layouts show a
scrubber only when dragging it has an effect.

Its clock readout and "jump to now" button go with it; the playhead
still marks the current time, as it does on mobile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UavT5bm5ZN2f1tNd7Yz1H
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.

2 participants