Commit Graph
4599 Commits
Author SHA1 Message Date
Miguel Angel 00ccb4b3c6 revert(registry): rehold map-territory-wipe after Miguel reject
Restore pre-port baseline SVG; CEILING_ONLY port is not done. Ungated
until Lead re-gates with Miguel OK. No MP4 wrapper.
2026-09-21 10:20:02 -04:00
Miguel Angel 0385967808 feat(registry): port QC_PASS map-border-chase
Alaska Highway reverse: basemap PNG plate + measured feeder draws and
MotionPath arrow. No MP4 wrapper. MIA credit documented.
2026-09-21 10:12:31 -04:00
Miguel Angel 093fd6d3a5 feat(registry): port QC_PASS map-shipping-lanes
City Water Supply 1941 reverse: topo plate + dam pop + tunnel draw.
Externalize topo plate asset (no base64, no MP4 wrapper). MIA credit and
declared ceilings documented.
2026-09-20 23:59:03 -04:00
Miguel Angel b4dc6924ad feat(registry): port QC-gated map-territory-wipe (CEILING_ONLY)
US wipe + cascade arrows from MIA oil-industry shot 49 reverse. SVG/GSAP
only; demo mp4 not shipped. NARA/FedFlix PD credit and fidelity ceilings
documented.
2026-09-20 23:32:13 -04:00
Miguel Angel 1a495f95ea feat(registry): port QC-gated map-arrow-fanout (CEILING_ONLY)
Berlin Airlift-style Germany SVG/GSAP from MIA reverse: sectors, divide
glow, thick W-E arrow fan-out + trails, endcard. CDN GSAP only (no local
gsap.min.js, no MP4 wrapper). Document credit and fidelity ceilings.
2026-09-20 23:13:57 -04:00
Miguel Angel d29d9fd87b feat(registry): port QC-gated map-radar-rings (CEILING_ONLY)
Self-contained SVG/GSAP multi-hub rings + iris-out from MIA Network
Broadcasting 1934 reverse. Timeline id aligned to map-radar-rings. No MP4
wrapper. Document credit and ceilings (grain, blocky coast, partial borders).
2026-09-20 23:12:59 -04:00
Miguel Angel 503a27cc1f fix(docs): commit catalog hashed assets for map-globe-inset
Include generator-emitted still hashes so catalog-drift stays green.
2026-09-20 22:56:00 -04:00
Miguel Angel 923c8dcfbe feat(registry): port QC-gated map-globe-inset (CEILING_ONLY)
Reverse winner from MIA Our Shrinking World 1946: seek-safe SVG/GSAP
globe rings + continent spin + plane behind disk, then switchboard still.
Stills only (clouds/switchboard); no MP4 wrapper. Document MIA credit and
fidelity ceilings (angular coasts, grain, SVG plane, lighting, still plate).
2026-09-20 22:55:22 -04:00
Miguel Angel 716dbc2a1c revert(registry): hold map-campaign-arrows until QC gate
Premature reverse-port landed before Map Reverse QC. Restore baseline
composition from 346b9d4f1; keep draft under video-reverse reversals/.
2026-09-20 22:30:20 -04:00
Miguel Angel dc144b6508 feat(registry): reverse-port map-campaign-arrows US rail web
Replace blob landmass with contiguous US silhouette (us-atlas nation) plus
Great Lakes and staggered east densify / transcontinental / west-spur draws
matching MIA How Our Country Grew grammar. Seek-safe GSAP; Tune vars for
palette and speeds; keep Moving Image Archive inspiration credit. No footage.
2026-09-20 22:29:31 -04:00
Miguel Ángel 346b9d4f1d revert(registry): restore code-drawn animated maps (drop footage wrappers) 2026-09-20 22:18:00 -04:00
Miguel Ángel e3b3f22be3 fix(registry): drop imitation framing from mia footage map pack
Rename map-* titles to real source films, scrub leftover trope copy,
and keep footage-only video compositions with MIA credits.
2026-09-20 22:09:08 -04:00
Miguel Ángel 2f8e0168c0 fix(docs): commit catalog item assets for animated map payloads
Catalog drift expected docs/public/catalog/items/map-*/assets clips
and posters to be tracked alongside the live player payloads.
2026-09-20 22:01:19 -04:00
Miguel Ángel 8bb3b0ddb6 docs(registry): align map pack credits with mia provenance
Use Moving Image Archive title/year credit lines, document
/api/clips download acquisition, and list sample NARA/Prelinger shots.
2026-09-20 21:52:57 -04:00
Miguel Ángel eeb447cdc6 feat(registry): mount MIA Animated Maps footage in map-* blocks
Replace schematic SVG map tropes with seek-safe public-domain archival
clips from Moving Image Archive (self-hosted assets + on-screen credit).
2026-09-20 21:48:36 -04:00
Miguel Ángel e20ecf49dd fix(docs): use live player previewSrc on animated map catalog pages
Regenerate map-* CatalogDetail MDX now that payloads exist so detail
pages mount /public/catalog/blocks/map-*.json instead of 403 CDN mp4/png.
2026-09-20 21:35:27 -04:00
Miguel Ángel 840c4b3684 fix(docs): regenerate animated map catalog payloads
Replace hand-copied map-*.json with output from
generate-catalog-payloads.ts so check:catalog-drift matches CI.
2026-09-20 21:35:27 -04:00
Miguel Ángel e463f04e4d fix(docs): add live catalog payloads for animated map blocks
Generate docs/public/catalog/blocks/map-*.json payloads and sync gallery
data/nav so check:docs-catalog passes and Mintlify can mount live players
instead of blank stills.
2026-09-20 21:35:26 -04:00
Miguel Ángel a69b8e6d0d fix(registry): format animated map HTML and ASCII credit copy
Unblock Catalog Previews by oxfmt-ing the 18 new map compositions.
Replace em dashes / \\u2014 with ASCII hyphens in manifests, HTML notes,
CREDITS, and catalog MDX. Normalize block params to key/text for the
catalog generator.
2026-09-20 21:35:26 -04:00
Miguel Ángel fcc7f5bb95 feat(registry): expand animated maps pack
Add 18 newsreel-style GSAP/HTML/SVG map tropes (front-line crawl, ink blot,
shipping lanes, globe inset, stamp labels, border chase, radar rings,
boundary morph, compass/scale, itinerary stops, hotspot pulse, parchment
fold, split theater, arrow fan-out, grid crosshair, plus isoline / conquest
fill / legend build). Updates registry, catalog docs, and MIA credits.
2026-09-20 21:35:25 -04:00
Miguel Ángel 1416ac28c1 feat(registry): add newsreel-style animated map blocks
Five original GSAP/HTML/SVG compositions for classic educational
cartography tropes (route draw-on, territory wipe, city pins, region
zoom, campaign arrows). Inspired by Moving Image Archive Animated Maps
collection aesthetics only — no footage or assets included.
2026-09-20 21:35:25 -04:00
Miguel Ángel c09594fef8 fix(docs): restore the sidebar gutter on catalog pages (#4238)
* fix(docs): restore the sidebar gutter on catalog detail pages

* fix(docs): align the catalog tab row and panes with the page gutter
2026-09-20 21:17:09 -04:00
Miguel Ángel b4787082b2 fix(studio): keep narrow timeline headers readable (#4232)
* fix(studio): hide narrow timeline header labels

* test(studio): cover narrow automation headers

* test(studio): cover narrow automation headers

* test(studio): assert narrow lane tooltip

* style(studio): format narrow header changes

* test(studio): cover narrow speed lane labels

* fix(studio): truncate timeline header names

* test(studio): remove stale header room plumbing
2026-09-20 19:49:29 -04:00
Miguel Ángel e86a530f0b fix(cli): update Puppeteer browser dependencies (#4233)
Bump @puppeteer/browsers to ^3.2.2 and puppeteer-core to ^25.10.0 so Chrome spawns detached only off Windows (upstream puppeteer #15415). Lock regenerated on top of main. Fixes #4028.
2026-09-20 18:08:16 -04:00
Miguel Ángel f92d017b99 feat(cli): add timeline editing verbs (#4221)
* feat(cli): add timeline editing verbs

* chore: satisfy fallow complexity gate

* test(cli): cover snap rounding at known fps

* test(cli): cover snap rounding at known fps

* test(cli): cover snap rounding at known fps

* test(cli): cover snap rounding at known fps

* fix(cli): derive split ids from stable refs

* fix(cli): remove timeline gate blockers

* fix(cli): close freshness and output gate gaps

* fix(cli): close remaining timeline review findings

* refactor(cli): simplify mutation decision flow

* refactor(cli): split trim and apply paths

* refactor(cli): isolate trim validation

* fix(cli): satisfy timeline lint gate

* style: format timeline mutation files

* fix(cli): preserve mutation result types

* fix(cli): close timeline receipt and nested bounds gaps

* fix(studio): rollback writes that fail during write

* fix(cli): bound moved clips by end time
2026-09-20 18:04:13 -04:00
Miguel Ángel 4ff4435e1d feat(studio): export player handle for hosts (#4227) 2026-09-20 17:47:35 -04:00
Miguel Ángel 2fb6f6987e test(studio): close shadow reload regression gaps (#4223)
* test(studio): close shadow reload regression gaps

* chore: refresh follow-up checks

* test: keep follow-up free of unused exports

* test(studio): distinguish root timeline selection
2026-09-20 17:15:29 -04:00
Vance Ingalls f09ea7af3b chore: release v0.8.57 (#4231) v0.8.57 2026-09-20 14:13:17 -07:00
Miguel Ángel e48eac7f5e feat(studio): track preview time to first frame (#4226) 2026-09-20 17:10:41 -04:00
Vance IngallsandClaude Code f476e78cc8 feat(studio): say which project this Studio is serving when a read 404s (#4228)
A tab whose project the running Studio cannot resolve failed every composition
read with a 404 and said nothing. `useSdkSession` recorded the failure to
telemetry and returned, so the editor looked normal while every edit silently
took the server path instead of the SDK — nothing landed, and nothing said
why. The `out-fb1` timeline sat in that state for an hour and a half, through
a reload that passed mount validation.

The banner names both sides: "This Studio is serving X. This tab was opened
for Y." That is the whole truth in the host that produces these 404s. The
CLI-embedded Studio serves exactly one project, and `hyperframes preview`
reuses port 3002 when the folder matches, so starting it on a different
folder takes the port out from under an open tab — and the project the old
tab was pointed at is untouched on disk. "No longer available" would be
false: nothing was lost, and the same timeline shows the project return.

With several projects served (the Vite host) a rename cannot be told from a
deletion, so that wording claims no cause.

`reportReadFailure` answers which project a failed read identifies. Only a 404
does; a 5xx, a dropped request, an unexpected body or an empty file say nothing
about which project the server serves, so none of them claim it.

Also fixes a smaller lie on the same path: `useFileTree` parsed the 404's JSON
body without checking `r.ok`, so the failure took the success branch and
recorded an empty tree — and an empty provenance snapshot for a project that
was never read — which then travelled out in bug reports.

The 404s continue; this ends the silent part, not the failure.

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-20 13:53:52 -07:00
Vance IngallsandClaude Code 74af8b4279 fix(studio): validate the hash on hashchange, and stop pushing dead ids (#4225)
The mount path has validated its hash since v0.8.52. The hashchange path did
not — it adopted whatever id the URL offered, so changing projects walked
straight around the mount fix and re-armed the same failure: every later
/api/projects/<id>/... request 404s for the life of the tab, including the
composition read that opens the SDK session.

Same tri-state as mount: ok adopts, unknown adopts (a transient check failure
must not rewrite a hash that is probably fine), missing falls back.

Three details that are not obvious from the diff:

- **The race guard reads the hash, not a counter.** This effect re-registers on
  every projectId change, so a `generation` in that closure resets and an
  in-flight verdict from the previous registration would read as current —
  adopting a project the user already navigated away from. The hash cannot go
  stale, so it is the ground truth to compare against after the await.

- **The fallback re-fetches the project list.** tryConnect has `data.projects`
  in scope; this path does not, and caching the mount value would be stale in
  exactly the long-lived-tab case this exists for.

- **Both rewrites use replaceState.** `location.hash =` pushes, leaving the dead
  id one Back away — and Back would then re-validate, fall back, and push again,
  forever. The mount fallback had this bug too; fixed here as well.

Also emits the hashchange verdict so the `unknown` branch — which deliberately
keeps the hash and is invisible from outside — is countable.

Verified: 7 mutants, 7 caught, including adopting without validating, collapsing
unknown into missing, trusting the verdict over the live hash, memoizing the
fallback list, and pushing on either rewrite. Full studio suite 5,323 passed.

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-20 13:53:31 -07:00
Vance IngallsandClaude Code 7abbe49638 feat(studio): a per-page tab id, and the hash-validation verdict (#4224)
Two telemetry gaps, both pure instrumentation with no behaviour change.

`tab_id` — distinct_id identifies a browser, not a page. Several
investigations stalled on "two tabs, or one tab switching?", because a burst
of events alternating between two project hashes is indistinguishable from a
user moving between projects, and `session_start` fires once per page load so
counting it cannot tell them apart either. One id per page load, from one
shared module, attached by BOTH transports (`studio:*` and `studio_*`) — those
two families already drifted once on how `agent_runtime` is encoded.

Deliberately not persisted. Storage would survive a reload and quietly turn
this into a tab-session id, changing what every query means, and blocked or
cleared storage would make the property intermittently absent — which reads as
"old client" rather than "storage unavailable".

`project_hash_validated` — a tab that keeps its hash because the check itself
failed ("unknown") is currently indistinguishable in production from one that
never ran the check. Different causes, different fixes, one silent bucket.
Emits `stage` (mount | hashchange), `outcome` (ok | missing | unknown), and
the HTTP status when the server answered.

Verified: 8 mutants, 8 caught — including dropping tab_id from either
transport, regenerating it per read, persisting it to storage, emptying it in
the no-DOM fallback, and collapsing "unknown" into "missing". Full studio
suite 5,314 passed.

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-20 13:30:35 -07:00
Miguel Ángel ab6cbb383e refactor(studio): compose timeline parts through TimelineProvider (#4222)
* refactor(studio): add timeline provider boundary

* fix(studio): preserve timeline context schema

* refactor(studio): own timeline composition context

* fix(studio): complete timeline context ownership

* refactor(studio): keep timeline helper exports in Timeline

* refactor(studio): extract timeline provider state builders

* refactor(studio): isolate timeline provider branches

* refactor(studio): extract timeline overlay state

* refactor(studio): extract timeline overlay state hook

* fix(studio): keep overlay internals out of context
2026-09-20 15:38:27 -04:00
Miguel Ángel 243ecd1d10 feat(studio): compose preview overlay guides (#4220)
* feat(studio): compose preview overlay guides

* fix(studio): sync late preview iframe mounts

* fix(studio): make preview iframe ownership explicit
2026-09-20 14:37:50 -04:00
Miguel Ángel 85a1a8f789 fix(studio): swap the preview via a shadow iframe instead of blanking on reload (#4207)
* fix(studio): reloading the preview swaps in a ready copy instead of blanking

* fix(player): report painted once assets are ready and the loader has faded out

assetsready fires when the loader fade starts, so a consumer promoting on it can
expose a raised or fading panel. painted fires once per document load after the
fade completes, with a getter for late listeners; it resets on each load.

* fix(studio): promote the shadow preview only when painted, under one wait budget

Studio waited for ready plus two frames, which can land while the player's loader
panel is still up or fading. It now also waits for the player's painted event.
The shadow no longer arms the 5s adapter give-up, so a slow but live load fails
only at the single 15s budget. Set-destination availability follows the live
iframe across a swap.
2026-09-20 13:04:23 -04:00
Miguel Ángel 0c158be165 feat(studio): edit a clip's speed ramp with a rate lane and presets (#4153)
* feat(studio): edit a clip's speed ramp with a rate lane and presets

* fix(studio): keep the live preview attribute in sync with what gets persisted

* fix(cli): snapshot picks the source frame through a rate lane and up to 10x

hyperframes snapshot clamped the constant playback rate at 5 and ignored a
rate lane in data-automation, so a sped-up or ramped clip showed the wrong
frame. It now reads the rate through the runtime's own reader and maps time
with the shared source-time function.

* docs(skills): teach speed ramps as a rate lane in one reference page

Adds the speed-ramps reference, corrects the 0.1 to 5 rate range to 0.1 to 10
and the 'speed ramps are not supported' lines in the skills, and regenerates
the skills manifest.

* docs(skills): say what fixes a ramped clip's length and how the render approximates pitch

* fix(studio): hide the speed preset until the clip has a known duration

A preset on a clip with no data-duration was stretched over a made-up one
second. The panel now offers presets only when the duration is known.

* docs(skills): fix the last stale ramp line in the editing recipes and the slice-cap wording
2026-09-20 12:21:47 -04:00
Miguel Ángel fa699b7d9b refactor(studio): move playback adapter resolution into its own module (#4199) 2026-09-20 12:07:53 -04:00
Miguel Ángel e45483aa0a chore: release v0.8.56 (#4218) v0.8.56 2026-09-20 10:57:46 -04:00
Miguel Ángel 41b7202bca refactor(studio): make timeline rows use manifest elements (#4213)
* refactor(studio): make timeline rows use manifest elements

* fix(studio): preserve timeline consumer prop names

* test(studio): update asset row expectation

* fix(studio): clear preflight lint nit

* chore: allow intentional timeline hook removal

* chore: refresh PR capture checks

* test(studio): align visibility gate with store rows

* test(studio): preserve canEdit literal type
2026-09-20 10:18:03 -04:00
Miguel Ángel 84e6531668 feat(studio): a host can make the preview read-only (#4200)
* feat(studio): a host can make the preview read-only

Gates the shared geometry commit funnel (offset/size/rotation), so the
inspector's manual X/Y/W/H/rotation fields refuse the same as canvas
drag/resize/rotate. The flat inspector's fields render disabled with a
host-supplied reason. Fixes a hooks-order crash on deselect found in review.

* refactor(studio): derive the read-only preview flag from context, not a synced store

EditorShell synced its readOnlyPreview prop into a zustand store via a raw
useEffect behind an eslint-disable of no-restricted-syntax. Replace it with
a PreviewReadOnlyProvider context computed fresh from the prop every render:
Tier-1 consumers (genuine EditorShell descendants) read it via useContext;
Tier-2 consumers (useAppHotkeys, useGestureCommit, the
useDomEditSession/useDomEditCommits/useDomGeometryCommits chain), which run
in App.tsx above EditorShell, take it as an explicit parameter threaded from
StudioApp's own prop instead. Adds a host-mount test proving EditorShell
gates its descendants from its own prop with no StudioApp/App.tsx involved.

* fix(studio): keep preview hotkeys context explicit

* fix(studio): close read-only interaction escapes

* fix(studio): preserve timeline ownership in read-only preview

* fix(studio): rollback read-only preview work

* fix(studio): keep hotkey ownership on the active surface

* fix(studio): format preview overlay and strengthen split test

* fix(studio): clear preview gate findings

* refactor(studio): share preview test fixtures
2026-09-20 10:10:57 -04:00
Miguel Ángel 9bd6da848f feat(studio): theme timeline colours with CSS tokens (#4211)
* feat(studio): the timeline's colours read CSS custom properties instead of literals

The timeline's surfaces, hairlines, ruler text and clip colours were hard-coded
hex/rgba, so a host with a light chrome saw a black slab. The 24 values move
into new --timeline-* tokens in theme.css (the same mechanism Button and Menu
already use), holding today's exact values, so a host themes the timeline by
overriding a token instead of the object staying opaque.

* fix(studio): route timeline clip colours through theme tokens

* chore(studio): bank the colour-literal baseline

* fix(studio): preserve timeline clip token defaults

* fix(studio): increase default timeline clip contrast

* fix(studio): keep timeline hover brighter

* test(studio): stabilize property panel flag coverage
2026-09-20 09:38:38 -04:00
Vance Ingalls 96369c8acd chore: release v0.8.55 (#4216) v0.8.55 2026-09-20 00:45:18 -07:00
Vance IngallsandClaude Opus 5 05d5abb926 feat(studio): a blocked GSAP edit records which of seven causes it hit (#4214)
`edit_blocked` is the largest interaction failure in Studio: 575 users in 14
days, 17% of everyone who touches an editing control, and 41% of them are
blocked on their first or second attempt. Only 30% ever commit anything
afterwards.

Almost all of it is one message. "This animation is computed at runtime. Edit
the animation in the Code tab." covers ~460 of those users and is thrown from
seven different places — runtime-dynamic provenance, unresolved keyframes, an
unresolved selector, a geometry commit over unresolved source, and three
live-tween-without-a-source-match cases across position and rotation. The
telemetry carried only that string, so the classes were indistinguishable.

The copy is also only literally true for the first. The rest are parser or
source-matching limits where the animation may be plain authored source, and
we send the user to the Code tab to rewrite something that could be fine.

Adds a `detail` beside the coarse reason, set at each of the seven origins and
emitted as `block_detail`. Telemetry only; nothing branches on it, and the
user-facing copy is unchanged. Which cause dominates decides whether the fix
is better copy, a parser gap, or an Unroll-style recovery path — and guessing
that without the split is what this is meant to avoid.

`directEditOutcomeForProperties` rebuilt blocked outcomes from `reason` alone,
so it dropped the detail for the three causes that flow through it; it now
carries it. Mutation testing caught that, not review.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 07:42:37 +00:00
Miguel Ángel c204b3c757 chore: release v0.8.54 (#4212) v0.8.54 2026-09-20 00:22:52 -04:00
Miguel Ángel 5262062e7b feat(studio): export timeline editing for a host outside EditorShell (#4201)
* fix(studio): undo waits for in-flight hand edits before consuming history

* docs(studio): trim comments to the 3-line house rule

* feat(studio): export timeline editing, undo/redo, conflict banner and TimelinePane for a host

* docs(studio): trim comments to the 3-line house rule

* refactor(studio): simplify exports pass

* docs(studio): trim comments to the 3-line house rule

* fix(studio): resolve canEdit targets against expanded timeline rows

* docs(studio): trim comment to the 3-line house rule

* fix(studio): guard razor-split-all and the audio-group attribute write

* fix(studio): gate the audio-group attribute for sub-composition-only members

* fix(studio): keep blocked timeline edits truthful

Refresh cached permission checks from the current host policy, gate auto-grouping before live patches, and restore optimistic attribute previews when a guarded commit is refused.

* fix(studio): publish desktop compatibility exports

Expose range selection and the canonical loader from the built Studio package while retaining timeline editing seams.

* fix(studio): gate timeline drop writes

Route asset, file, and composition drops through the shared edit permission gate before any side effect.

* fix(studio): preserve release-owned style exports

* fix(studio): clear file and Fallow gates

* fix(studio): keep the carve gate and range slice under the size budgets

* style(studio): format playerStore
2026-09-19 23:59:13 -04:00
Vance Ingalls d11907c325 chore: release v0.8.53 (#4210) v0.8.53 2026-09-19 20:30:29 -07:00
Vance IngallsandClaude Fable 5.1 83624d193e fix(studio): stop counting extension-leaked AbortError rejections as errors (#4209)
`studio:unhandled_promise_rejection` was dominated by two AbortError rows
("signal is aborted without reason", "The user aborted a request."), about
10,000 events across 265 users per week, every one with a stack rooted in
the thumbnail scheduler's `release()`.

The scheduler is correct: every promise on its abort signal is awaited, and
a Chrome 153 reproduction with the real bundle confirms that releasing a
lease mid-fetch produces no unhandled rejection on its own. The leak comes
from browser extensions that wrap `window.fetch` in the main world
(Requestly and Poper Blocker are two seen in field stacks) and derive a
promise from each request without a rejection handler. Chrome rejects that
derived promise with our abort reason, so the stack points at `release()`.
Injecting such a wrapper reproduces the field rows exactly. Among users
with both this leak and a "Failed to fetch" error, 69 of 72 have an
extension frame inside `window.fetch`; among the rest, 13 of 308.

An AbortError rejection is a cancellation we asked for, so record it as a
sampled `abort_rejection_filtered` counter instead of an error. The filter
sits before the ERROR_CAP check: a scroll burst previously spent the
50-event cap in under a second and dropped every real rejection for the
rest of the session (116 users hit the cap in the last 7 days).

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 19:51:00 -07:00
Vance IngallsandClaude Opus 5 90e67ff5a8 fix(release): a stale local tag can no longer silently widen the changelog (#4208)
* fix(release): a stale local tag can no longer silently widen the changelog

`release:prepare` creates `vX.Y.Z` locally at the branch commit, then the
publish workflow creates the same tag at the merge SHA. After a squash merge
those are different commits and the local one is not an ancestor of main, so
`git describe` cannot reach it and falls back to the previous release without
saying so. `git fetch --tags` does not help: it refuses to move a tag that
already exists locally, and only `--force` does.

On the v0.8.52 cut this produced a `v0.8.50...v0.8.52` draft that re-listed
about a hundred already-shipped commits. The changelog is user-facing, and
nothing in the output said the baseline was wrong.

Adds a guard in the drafter: after resolving the baseline, fail if a stable
tag exists locally that sits strictly between it and the release. `describe`
only ever returns a reachable tag, so a tag in that range is proof it skipped
one. The message names the tags and prints `git fetch origin --tags --force`.
`--from` still overrides, for a deliberate baseline.

Also prints the captures-gate hatch with the release PR instructions. Every
release bumps packages/studio and packages/player, which
scripts/check-pr-captures.mjs watches, so it demands before/after media for a
version bump; its own `## No visible change` section is the intended answer
and applies to a 4-line package.json diff.

Verified end to end by moving a local tag onto the pre-squash commit of
v0.8.51: the drafter fails with the fix, and drafts normally once restored.
Six mutants of the range check are all caught by the tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(release): guard the baseline inside its resolver

`resolvePreviousTag` only runs when `--from` was not supplied, so testing
`!options.from` at the call site repeated a condition the `??` already
expressed. Folding the guard into the resolver drops that branch, which had
pushed `createDraft` to a CRAP score of exactly 30.0 and failed the audit.

The assert sits outside the try/catch on purpose: `fail` exits rather than
throwing today, and this keeps a catch from ever swallowing the guard if that
changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 17:14:51 -07:00
Miguel Ángel 2d84937e1f feat(studio): export the header logo, button sizes and a stable styles.css subpath (#4197)
A host mounting Studio's header reuses HyperframesLogo and buttonSizes for
matching chrome, and imports the compiled stylesheet as a fixed-name package
export instead of a hash that changes on every rebuild.
2026-09-19 19:06:01 -04:00
Miguel Ángel 98e8372a0a feat(studio): all panels move onto the dockable, persisted layout (#4192)
* feat(studio): every Studio panel is a dock panel and the shell moves onto it

EditorShell, App.tsx, the left and right panel columns, and the
inspector hooks now render through Dock.Panel instead of the old
fixed-width sidebars and split-pane inspector. usePanelLayout becomes
a thin facade over the dock store; fitPanels, the resize divider and
the split-inspector hook are dead and removed.

* refactor(studio): drop dead inspector flags and stale fallow entries after dock migration

Collapse inspectorButtonActive into inspectorPanelActive, remove unread
layers/design active fields, un-export unused prop types, flatten a nested
ternary, and point fallow ignores at the renamed/deleted files.

* fix(studio): keep right-panel behaviours the dock migration dropped

Block params dismiss on selection or when Design is hidden, the slideshow
panel can be closed, the selection outline follows the inspector intent,
caption editing focuses Design, and a URL slideshow tab opens once the
panel exists.

* refactor(studio): name the inspector-open state once in the header

* fix(studio): right-panel behaviours survive selection, restore and reopen after the dock migration

Block params stay open over an element that was already selected, caption mode wins over block params, the slideshow panel waits for the dock, the Renders tab title survives a reopen, each side column remembers the tab it last showed, tab switches are reported only when the tab changes, and the e2e thumbnail selector follows the dock.

* docs(studio): describe the dock layout and point comments at the renamed right panels

* fix(studio): block params survive a preview reload of the selected element, and panel titles are covered

The dismissal compares the selected element by its target key instead of object identity, because a preview reload rebuilds the selection for the same element. A closed and reopened panel keeps its custom title, with a test, and the e2e selector is formatted.

* fix(studio): block params leave with any other tab pick, and a restored slideshow placement survives loading

Block params are now dismissed by the tab setter the way their old tab was, including a re-pick of the same element, instead of by comparing selections. The slideshow panel is only closed after a file that was a slideshow stops being one, so a restored layout is not discarded while the file loads.

* refactor(studio): drop the duplicate inspector-open computation

The rebase onto main left two identical inspectorButtonActive/inspectorOpen
constants after a conflict resolution; only the first was still referenced.

* chore(studio): allow the dock migration deletions in the guard

StudioLeftSidebar.tsx, LeftSidebar.tsx and its storage test,
TimelineResizeDivider.tsx, useInspectorSplitResize.ts and fitPanels
are replaced by the dock; the CI deletion guard needs each named
with its reason.

* style(studio): oxfmt the deletion-guard entries

* fix(studio): rename Timeline's aria-label so it does not collide with its dock panel wrapper

Every dock panel wrapper now carries aria-label=<title>, matching the "Timeline"
title for the timeline panel. Timeline.tsx also set aria-label="Timeline" on its
own content div, so `document.querySelector('[aria-label=\"Timeline\"]')` found
the dock wrapper first and the timeline viewport gate CI check timed out waiting
for an attribute the real content div actually carried. Renamed to "Timeline
track view" and updated every consumer: the production diagnostics selector,
its tests, and the e2e viewport-gate script.

* fix(studio): restore inspectorButtonActive as a real prop after a rebase regression

Rebasing PR2 onto main replayed an older local commit whose diff did not
textually overlap main's newer inspectorButtonActive prop, so git applied
it with no conflict marker and silently reverted the header to computing
the value itself instead of receiving it. StudioHeader.dom.test.tsx already
asserts the prop is respected; restoring it makes that test meaningful
again. App.tsx now computes it once (panelLayout.rightCollapsed is already
the dock's effective collapse state, no separate railed-by-width case) and
passes it down.

* chore(studio): wrap a long doc comment in useRightPanelIntents
2026-09-19 19:04:16 -04:00