mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* feat(tweaks): bind toolbar toggle to artifact panel
Wires the host viewer's Tweaks toggle to artifact panels via two
protocols: postMessage __edit_mode_* for agent-generated .twk-panel
artifacts, and the existing class-based bridge for tweaks-skill
.tw-panel artifacts. Toggle disables itself when the artifact exposes
no panel, syncs state when the user closes the panel locally, and no
longer regenerates srcdoc on toggle (no iframe remount).
- bridge: emit od:tweaks-available + MutationObserver-driven
od:tweaks-panel-state so host learns availability and mirrors local
closes; transform-based hide preserves the artifact's transition
- host: listen for both od:tweaks-* (bridge) and __edit_mode_*
(artifact) dialects, send both on toggle, disable button when no
panel, reset state on file change
- skill: document the two protocols as a host integration contract
- i18n: add fileViewer.tweaksUnavailable for all 13 locales
* fix(tweaks): keep srcDoc path for `.tw-panel` artifacts
The class based tweaks template (`.tw-panel` / `.tw-hidden`) needs the
bridge `injectTweaksBridge` emits, but the default plain HTML preview
URL loads the iframe, which bypasses buildSrcdoc entirely. Without the
bridge there is no `od:tweaks-available` ping, so the toolbar toggle
stays disabled on first load of a tweaks template artifact unless an
unrelated mode (palette, inspect, etc.) coincidentally forces srcDoc.
Add a `tweaksBridge` flag to `shouldUrlLoadHtmlPreview` and detect the
fixed `.tw-panel` / `.tw-hidden` template selectors in the artifact
source via a new `hasTweaksTemplate` helper. FileViewer passes the
detected flag through so tweaks template artifacts pick the srcDoc
render path on first load.
Tests in `file-viewer-render-mode.test.ts` cover the new disqualifier,
the helper positive and negative cases, and combinations with the
existing flags.
* fix(tweaks): resolve v0.7 UI ambiguity between toolbar toggle and palette
After rebasing onto v0.7, three problems surfaced. v0.7 ships a palette
popover button also labeled `Tweaks` with the same sliders icon as the
new toolbar toggle. Toggling that popover flipped render mode (URL load
to srcDoc) and reloaded the iframe, flashing the preview. The resulting
iframe remount caused agent protocol artifacts to re-announce
`__edit_mode_available`, which flipped the toolbar toggle back on
without user input.
Rename the palette popover button to `Themes` (button label, dialog
title, `aria-label`) and swap its icon to a new `paint-bucket` glyph.
The artifact tweaks toggle keeps the `tweaks` sliders icon. Internal
identifiers (`data-testid="palette-tweaks-toggle"`, CSS classes, the
`PaletteTweaks` component name) stay stable so existing tests and
styles still target the same elements.
Drop the auto set true on `__edit_mode_available`; that signal now
flips `tweaksAvailable` only. `syncBridgeModes` posts the current
`tweaksMode` to the artifact (both the bridge dialect and
`__activate_edit_mode` / `__deactivate_edit_mode`) on every iframe
load so the panel matches the toolbar.
Mount both URL load and srcDoc iframes simultaneously, absolutely
positioned and overlapping, with CSS visibility flipping between
them. Toggling render mode no longer reloads the iframe so there is
no flash. `isOurIframe(source)` accepts messages from either iframe
so startup announcements from the hidden iframe are not lost; six
receive filter sites switch from `iframeRef.current?.contentWindow`
to the helper. Sends still target `iframeRef.current`, kept aligned
with the active iframe via a `useEffect`, and a `syncBridgeModesRef`
pushes current bridge state to the now visible iframe whenever the
render mode flips.
Tests that previously asserted exclusive render mode (`url-load`
vs. `srcdoc` presence) now assert the active `data-testid` sits on
the expected iframe via a co-attribute regex. The Draw bar element
picking test switches from a cached frame reference to a `getFrame()`
helper since `data-testid` follows the active iframe across toggles.
Add a `paint-bucket` entry to `Icon` (Lucide style stroke icon).
* fix(tweaks): scope `od:tweaks-available` to the active iframe
The dual iframe setup mounts both the URL load and srcDoc iframes at
once and accepts postMessage events from either via `isOurIframe`. The
srcDoc iframe always carries the always injected tweaks bridge, which
runs `document.querySelector('.tw-panel')` on mount and posts
`{ type: 'od:tweaks-available', available: false }` for any artifact
that does not ship the class based panel. For an agent protocol
artifact (`.twk-panel`, `__edit_mode_*`), the URL load iframe correctly
announces `__edit_mode_available` and the host sets
`tweaksAvailable = true`. The hidden srcDoc iframe's `available: false`
ping arrives shortly after and overrides that to false, silently
disabling the toolbar button.
Scope `od:tweaks-available` to the active iframe only by re-checking
`ev.source === iframeRef.current?.contentWindow` before applying it.
`__edit_mode_available` and `__edit_mode_dismissed` stay accepted from
either iframe so the artifact's own announcement still drives the
toolbar toggle across render mode flips.
Spotted by Siri-Ray on PR #1643.
* fix(tweaks): start the toolbar toggle ON when the artifact mounts its panel visible
Both tweaks dialects (the class-based `.tw-panel` skill template and the
`.twk-panel` agent-generated edit-mode protocol) mount their panel visible
by default. Before this change the toolbar `Tweaks` toggle started in the
OFF state regardless, so the user saw the panel but had to click
toggle-on → toggle-off to actually hide it — confusing because the toggle
disagreed with what they could plainly see in the preview.
Two changes wire the initial state through to the toolbar:
- `srcdoc.ts` (class-based dialect): the tweaks bridge's `onReady` now
fires `postState()` alongside `postAvailability()`. `postState()` reads
`!panel.classList.contains('tw-hidden')` and posts the artifact's actual
initial visibility, so the host's existing `od:tweaks-panel-state`
handler picks it up and mirrors it into `tweaksMode`. Previously only
MutationObserver-driven changes were posted, so the host never learned
the artifact's initial state.
- `FileViewer.tsx` (twk-panel dialect): the agent dialect's
`__edit_mode_available` carries no visibility payload, so we infer
default-open from the fact that the artifact bothered to announce
availability at all (the SDK pattern is `useState(true)`). Mirror that
into `tweaksMode` exactly once per file (tracked by
`firstEditModeAvailableSeenForFileRef`), so an iframe remount triggered
by, e.g., flipping render mode through the Themes popover does not snap
a user-driven OFF back to ON.
Also fix a runtime `ReferenceError: panel is not defined` regression
this same change introduced when first written with backticks inside the
new code comment — the comment lived inside a `\`...\``-delimited script
template literal, so the embedded backticks closed and reopened the outer
literal and broke the bridge's JS body. Replaced with plain text.
Validation: web typecheck clean, 1597/1597 tests pass. Manually verified
with a `.twk-panel` artifact: open file → tweaks toggle is ON, panel
visible → one click hides both.
* fix(tweaks): seed bridge state from the panel's authored class, not from the host hidden attribute
The bridge installs `data-od-tweaks-hidden` on `<html>` synchronously in
`<head>` so the panel never flashes on initial paint. That attribute is
therefore *always* present by the time `onReady()` fires, which meant the
previous `applyClassesToPanel(!hasAttribute(...))` call unconditionally
forced `.tw-hidden` onto the panel, the follow-up `postState()` read that
forced-hidden class, and the host saw `visible: false` even when the
artifact had authored the panel as default-visible. The PR-1643 attempt
at "start ON when the artifact mounts visible" therefore still reported
OFF for the class-based template path.
Read the panel's authored class state first (the artifact body has just
parsed, so the panel's class is what the artifact wrote and nothing
else has touched it yet), then drive the attribute, the applied class,
and the `od:tweaks-panel-state` post from that captured value:
```ts
var panel = panelEl();
var initialVisible = !!panel && !panel.classList.contains('tw-hidden');
document.documentElement.toggleAttribute('data-od-tweaks-hidden', !initialVisible);
applyClassesToPanel(initialVisible);
attachObserver();
postAvailability();
postState();
```
A default-visible `.tw-panel` now reports `visible: true` on mount, the
host mirrors that into `tweaksMode = true`, and the toolbar Tweaks toggle
starts in the ON state instead of disagreeing with what the user sees in
the preview. The `.twk-panel` agent-protocol path is unaffected; its
initial-state mirror still goes through the
`firstEditModeAvailableSeenForFileRef` guard in `FileViewer.tsx`.
Surfaced by Siri-Ray in https://github.com/nexu-io/open-design/pull/1643#discussion_r3263571196.
Validation: web typecheck clean, 1597/1597 tests pass.
* fix(tweaks): re-mirror __edit_mode_available default-open state when switching .twk-panel files
The once-per-file guard that mirrors a `.twk-panel` artifact's
default-open state into the toolbar `tweaksMode` lives inside a
`window.addEventListener('message', ...)` handler installed in a
`useEffect(..., [])` with an empty dep list. The handler therefore
closed over the first-render `file.name`. After opening one
`.twk-panel` artifact, `firstEditModeAvailableSeenForFileRef.current`
got set to that first file; switching to a second `.twk-panel` file
left the message listener still comparing the new artifact's
`__edit_mode_available` against the stale captured name, so the
guard never re-fired and the toolbar stayed OFF while the new
artifact's panel was clearly visible — exactly the mismatch the
guard was supposed to prevent on initial load.
Add `file.name` to the listener effect's dep list so the handler
gets a fresh closure on every file switch. The bridge-message setters
(`setTweaksAvailable`, `setTweaksMode`), `isOurPreviewIframeSource`,
and `firstEditModeAvailableSeenForFileRef` are stable across renders,
so re-binding the listener has no other side effects beyond updating
the captured `file.name`.
Surfaced by Siri-Ray in
https://github.com/nexu-io/open-design/pull/1643#discussion_r3266838151.
Red-spec regression test added: `FileViewer tweaks toolbar > mirrors
__edit_mode_available default-open state for each switched-to
.twk-panel file`. Verified to go red on the bug (deps `[]`) and green
on the fix (deps `[file.name]`).
Validation: web typecheck clean, 1598/1598 tests pass (was 1597).
* i18n(tweaks): add fileViewer.tweaksUnavailable to the remaining 6 locales
The toolbar's disabled-Tweaks tooltip key landed in 13 locale files but
6 were missed (ar, fr, id, it, th, uk). Those locales were still falling
through to the English string via the `...en` spread, which contradicts
the repo convention that every key be defined explicitly in each locale.
Add the translation alongside the existing `fileViewer.tweaks` entry so
the full set of 19 locales now ships native copy for the disabled state.
Surfaced by Siri-Ray in
https://github.com/nexu-io/open-design/pull/1643#discussion_r3267654385.
* fix(tweaks): respect default-closed dynamic panels in __edit_mode_available
Protocol A in `design-templates/tweaks/SKILL.md` documents that the
artifact may default the panel to either open or closed and the host
should sync its toolbar toggle to whichever state the artifact reports.
The previous handler ignored that and unconditionally mirrored
availability into `tweaksMode = true`, so a default-closed dynamic
artifact would be force-opened the moment `syncBridgeModes` ran and
fired `__activate_edit_mode` — the artifact could not stay closed even
though the contract said it could.
Extend the message shape so the artifact can report its initial state
on the same payload:
{ type: '__edit_mode_available', visible?: boolean }
The host now reads `data.visible`:
- omitted → treat as `true` (back-compat: existing artifacts
emitting the legacy zero-arg shape mount with the
panel already on screen, which is the SDK pattern
`useState(true)`).
- `visible: true` → toolbar starts ON.
- `visible: false` → toolbar starts OFF, panel stays closed; the user
opts in by clicking the toggle, which then fires
`__activate_edit_mode` via the existing
`syncBridgeModes` path.
Update `design-templates/tweaks/SKILL.md` to document the new optional
field alongside the legacy shape.
Surfaced by Siri-Ray in
https://github.com/nexu-io/open-design/pull/1643#discussion_r3269955351.
Red-spec regression test added: `FileViewer tweaks toolbar > respects
__edit_mode_available { visible: false } for default-closed dynamic
artifacts`. Verified red without the fix (always-true mirror) and green
with the fix (`data.visible !== false`).
Validation: web typecheck clean, 1599/1599 tests pass.
|
||
|---|---|---|
| .. | ||
| audio-jingle | ||
| blog-post | ||
| clinical-case-report | ||
| critique | ||
| dashboard | ||
| dating-web | ||
| dcf-valuation | ||
| digital-eguide | ||
| docs-page | ||
| email-marketing | ||
| eng-runbook | ||
| finance-report | ||
| flowai-live-dashboard-template | ||
| gamified-app | ||
| github-dashboard | ||
| guizang-ppt | ||
| hr-onboarding | ||
| html-ppt | ||
| html-ppt-course-module | ||
| html-ppt-dir-key-nav-minimal | ||
| html-ppt-graphify-dark-graph | ||
| html-ppt-hermes-cyber-terminal | ||
| html-ppt-knowledge-arch-blueprint | ||
| html-ppt-obsidian-claude-gradient | ||
| html-ppt-pitch-deck | ||
| html-ppt-presenter-mode-reveal | ||
| html-ppt-product-launch | ||
| html-ppt-taste-brutalist | ||
| html-ppt-taste-editorial | ||
| html-ppt-tech-sharing | ||
| html-ppt-testing-safety-alert | ||
| html-ppt-weekly-report | ||
| html-ppt-xhs-pastel-card | ||
| html-ppt-xhs-post | ||
| html-ppt-xhs-white-editorial | ||
| html-ppt-zhangzara-8-bit-orbit | ||
| html-ppt-zhangzara-biennale-yellow | ||
| html-ppt-zhangzara-block-frame | ||
| html-ppt-zhangzara-blue-professional | ||
| html-ppt-zhangzara-bold-poster | ||
| html-ppt-zhangzara-broadside | ||
| html-ppt-zhangzara-capsule | ||
| html-ppt-zhangzara-cartesian | ||
| html-ppt-zhangzara-cobalt-grid | ||
| html-ppt-zhangzara-coral | ||
| html-ppt-zhangzara-creative-mode | ||
| html-ppt-zhangzara-daisy-days | ||
| html-ppt-zhangzara-editorial-tri-tone | ||
| html-ppt-zhangzara-grove | ||
| html-ppt-zhangzara-long-table | ||
| html-ppt-zhangzara-mat | ||
| html-ppt-zhangzara-monochrome | ||
| html-ppt-zhangzara-neo-grid-bold | ||
| html-ppt-zhangzara-peoples-platform | ||
| html-ppt-zhangzara-pin-and-paper | ||
| html-ppt-zhangzara-pink-script | ||
| html-ppt-zhangzara-playful | ||
| html-ppt-zhangzara-raw-grid | ||
| html-ppt-zhangzara-retro-windows | ||
| html-ppt-zhangzara-retro-zine | ||
| html-ppt-zhangzara-sakura-chroma | ||
| html-ppt-zhangzara-scatterbrain | ||
| html-ppt-zhangzara-signal | ||
| html-ppt-zhangzara-soft-editorial | ||
| html-ppt-zhangzara-stencil-tablet | ||
| html-ppt-zhangzara-studio | ||
| html-ppt-zhangzara-vellum | ||
| hyperframes | ||
| ib-pitch-book | ||
| image-poster | ||
| invoice | ||
| kami-deck | ||
| kami-landing | ||
| kanban-board | ||
| last30days | ||
| live-artifact | ||
| live-dashboard | ||
| magazine-poster | ||
| meeting-notes | ||
| mobile-app | ||
| mobile-onboarding | ||
| motion-frames | ||
| open-design-landing | ||
| open-design-landing-deck | ||
| orbit-general | ||
| orbit-github | ||
| orbit-gmail | ||
| orbit-linear | ||
| orbit-notion | ||
| pm-spec | ||
| pricing-page | ||
| replit-deck | ||
| saas-landing | ||
| simple-deck | ||
| social-carousel | ||
| social-media-dashboard | ||
| social-media-matrix-tracker-template | ||
| sprite-animation | ||
| team-okrs | ||
| trading-analysis-dashboard-template | ||
| tweaks | ||
| video-shortform | ||
| waitlist-page | ||
| web-prototype | ||
| web-prototype-taste-brutalist | ||
| web-prototype-taste-editorial | ||
| web-prototype-taste-soft | ||
| weekly-update | ||
| wireframe-sketch | ||
| x-research | ||
| AGENTS.md | ||