mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
1c7233ef1047e4fe2acca22ecaff64e0f44083e3
945
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1c7233ef10 |
fix(landing-page): speed up landing-page CI builds (#2734)
* fix(landing-page): speed up landing-page CI builds * fix(landing-page): disable dev-only landing caches Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(landing-page): reuse previews across CI runs * fix(landing-page): hash shared preview dependencies Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(landing-page): skip missing preview html reads Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(landing-page): rerun previews on cache hits Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(ci): repair landing-page workflow cache keys |
||
|
|
a3872b97a9 |
fix(tools-dev): preserve web origin trust on web start (#2715)
* fix(tools-dev): preserve web origin trust on web start Restart daemon/web when the trusted web port is missing, and reuse the active web port during repeated starts so run web and start web keep app-config origin checks aligned. Generated-By: looper 0.0.0-dev (runner=worker, agent=opencode) * fix(plugins): refresh official registry bundled count Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(tools-dev): preserve daemon/web reserved ports Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(tools-dev): preserve daemon reuse on web start Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(tools-dev): preserve running daemon port on web reuse Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(tools-dev): reserve explicit web port before daemon allocation Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * test(web): stabilize media provider reload flash timing Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(web): restore merged reattach workspace coverage Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * fix(tools-dev): reserve allocated daemon port Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * test(e2e): wait for artifact manifest persistence Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) |
||
|
|
b24168b340 | fix conversation title editing (#1926) | ||
|
|
ce2e7a0e66 |
fix(web): chat pane preserves scroll position when todo card grows (#2299)
* fix(web): chat pane preserves scroll position when todo card grows The PinnedTodoSlot renders outside the chat-log scroll container. When the todo card grows (new tasks added via TodoWrite), the scroll container's clientHeight shrinks in the flex layout, drifting the user away from the bottom. The existing ResizeObserver only observed children of the chat-log div, so pinned-todo growth was invisible to followLatestIfPinned. Fix: pass a containerRef to PinnedTodoSlot and observe that element in the same ResizeObserver. syncPinnedTodo() is called on effect setup and from the MutationObserver callback so observation stays current as the slot appears and disappears across TodoWrite snapshots. Red spec: apps/web/tests/components/chat-todo-autoscroll.test.tsx * fixup! fix(web): chat pane preserves scroll position when todo card grows Clarify test comment: the second test confirms followLatestIfPinned snaps scroll to bottom when fired. The structural guarantee (pinned-todo element is observed) is separately asserted in test 1, which is the check that goes red on main without the fix. * fix(web): correctness extend MutationObserver to pane ancestor for PinnedTodoSlot mount detection The MutationObserver was only watching the .chat-log element. PinnedTodoSlot (.chat-pinned-todo) is a sibling of .chat-log-wrap inside .pane, outside the observed subtree. syncPinnedTodo inside the MutationObserver callback was therefore dead code for mount/unmount transitions of the slot. Add a second observation on paneEl (el.parentElement?.parentElement) with childList-only so the MutationObserver fires when PinnedTodoSlot mounts or unmounts and syncPinnedTodo can register/deregister the element with the ResizeObserver. * test(e2e): chat pane auto-scroll on todo card growth Add Playwright spec that goes red on origin/main and green on this fix branch. Scenario A asserts that a chat-log pinned to the bottom snaps back after the PinnedTodoCard grows (the ResizeObserver-on-pinned-todo path). Scenario B asserts that a deliberate scroll-up is not overridden. Also allow OD_WORKSPACE_ROOT env override in next.config.ts so Turbopack resolves node_modules correctly when the web app is booted from a worktree whose node_modules symlinks resolve outside the default workspace root. * docs(agents): note pinned-todo observer coverage in chat UI conventions PinnedTodoSlot sits outside the .chat-log scroll container, so the ResizeObserver and MutationObserver coverage that keeps auto-scroll working when the todo card grows is non-obvious to future implementers. Document the invariant in the Chat UI conventions section. * fix(web): validate OD_WORKSPACE_ROOT, harden autoscroll test precondition * fix(web): validate OD_WORKSPACE_ROOT existence, make autoscroll precondition unconditional * fix(web): throw on invalid OD_WORKSPACE_ROOT instead of warn-and-fallback * fix(web): require pnpm-workspace.yaml at OD_WORKSPACE_ROOT, drop dead test branch Three follow-ups to nettee's review feedback: 1. apps/web/next.config.ts gains a pnpm-workspace.yaml existence check after the relative-path validation. Without it, an override like '<repo>/apps' or '<repo>/apps/web' passes the relative(resolved, WEB_ROOT) check but the resolved path is missing the sibling packages/* directory that apps/web imports from (for example @open-design/contracts). Next would later fail deep inside file tracing / Turbopack with a much harder-to-diagnose error. Now we throw at config load with a clear message. 2. e2e/ui/chat-todo-autoscroll.test.ts drops the redundant 'if (scrollUpOccurred)' branch. The hard precondition above it already guarantees distanceAfterScroll > 80, so the if was dead code that read as a false-green path. The body now runs unconditionally. 3. Same test tightens the post-grow assertion. The previous toBeGreaterThan(60) would pass even if a regression dragged the log most of the way back to the bottom (e.g. before=150, after=61). Replaced with Math.abs(distanceAfterGrow - distanceAfterScroll) less than SCROLL_PRESERVATION_TOLERANCE_PX (20) — a delta check that actually verifies the comment's claim of 'within ~20px of where the user left it'. * fix(web): canonicalize workspace root with realpathSync and tighten scenario B assertion - Use realpathSync on both resolved and WEB_ROOT before the ancestor check so that symlinked paths (macOS /tmp vs /private/tmp, worktree checkouts) compare correctly instead of false-throwing on a physically valid override. - Add isAbsolute(rel) guard for the Windows cross-drive case where path.relative() returns an absolute path instead of a ..-prefixed string. - Scenario B: replace distance-to-bottom delta assertion with scrollTop preservation check. Growing the pinned todo naturally increases distance-to-bottom by ~extraPx (clientHeight shrinks while scrollTop is held fixed), so the old Math.abs(after - before) < 20 check would fail on correct behavior. asserting scrollTop directly catches the real regression: followLatestIfPinned incorrectly snapping a non-pinned user back to the bottom. - Add hard precondition that clientHeight actually changed so the test fails fast if the layout stops exercising the non-pinned path. * test(e2e,web): add clientHeight guard to scenario A and mount-wiring unit test --------- Co-authored-by: Patrick A <eefynet@users.noreply.github.com> Co-authored-by: Patrick A <259201958+eefynet@users.noreply.github.com> |
||
|
|
ac0a9212fe | fix(web): restore HTML preview after source toggle (#2699) (#2710) | ||
|
|
f2703c8cb3 |
blog(landing-page): announce Open Design 0.8.0 plugin engine rebuild (#2731)
* blog(landing-page): announce Open Design 0.8.0 plugin engine rebuild
Adds the Product (announcement) post for the open-design-v0.8.0 release
(305 PRs · 75 contributors · 7 days; tag
|
||
|
|
829fc01c1c |
feat(landing-page): detail pages — interactive preview, share row, dual CTAs (#2679)
* feat(landing-page): detail pages — interactive preview, share row, dual CTAs
Joey requested three additions to every `/skills/<slug>/` and
`/templates/<slug>/` detail page, with opendesigner.io's skills
catalog and youmind.com's seedance prompt page as references.
What
- **Interactive preview**: a `<details>` toggle below the static thumb
reveals an `<iframe sandbox>` rendering the canonical artifact
(`/skills/<slug>/example.html` for skill-template origins,
`/templates/<slug>/template.html` for live-artifact origins). The
iframe loads lazily — only on first toggle — so the page stays fast.
An "Open in new tab ↗" pill on top-right of the frame links to the
same URL standalone.
- **Six-channel share row**: Reddit, X, LinkedIn, Facebook, Email,
Copy-link. Each anchor is a vendor "intent" URL (no tracker SDKs);
the copy-link button uses the Clipboard API with a `prompt()`
fallback for older Safari / embedded webviews. Wired by a small
handler appended to `header-enhancer.astro`.
- **Two primary CTAs** in the detail-actions row:
- "Use this skill →" / "Use this template →" routes to
`/quickstart/?skill=<slug>` (or `?template=<slug>`). The OD
desktop client has no public protocol handler yet, so a
`od://skill/<slug>` deep link would 404. Quickstart is the v1
pivot; once the client registers a scheme, the anchor flips to
a JS try-`od://`-then-fallback without changing the page surface.
- "Find on GitHub →" deep-links into the source folder.
Share copy keeps "open-source Claude Design alternative" front and
center across every channel — same brand keyword Google associates
with the homepage and `/alternatives/claude-design/`, so each social
click reinforces the same entity claim. Per-skill name + summary
follow so a reader who lands on a friend's tweet has a concrete
reason to click.
- X intent: "I'm using <skill> from @opendesignai — the open-source
Claude Design alternative.\n\n<description>"
- Reddit submit title: "<skill> — open-source Claude Design alternative"
- Email subject: same as Reddit; body: "I thought you'd like this —
<skill>, an open-source Claude Design alternative skill from Open
Design.\n\n<description>\n\n<url>"
- LinkedIn / Facebook: URL-only (those vendors auto-fetch OG meta,
so they read the existing canonical title + image).
Surface area
- Marketing site only. `apps/landing-page/app/pages/skills/[slug].astro`,
`pages/templates/[slug].astro`, `_components/header-enhancer.astro`,
`sub-pages.css`.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces.
- No new top-level dependencies. WeChat QR was dropped from the v1
scoping in favor of Joey's revised channel set; brings Reddit and
Facebook in instead.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: `/skills/deck-swiss-international/` shows all six share
buttons, both CTAs, and the iframe `<details>` toggle. Same on
`/templates/magazine-poster/`.
- Local dev: Reddit submit URL contains the SEO keyword in the title
param; X intent URL contains the @opendesignai mention + keyword
in the tweet body; Email mailto: subject + body wired correctly.
Followups
- Once OD desktop client registers a `od://` scheme, flip the "Use
this skill" anchor to JS-driven try + fallback so installed users
bypass /quickstart/.
- Translate the share copy + CTA labels across the 18 landing
locales (currently English-only).
- `i18n.ts` `ui.catalog.skills` keys could absorb the share-copy
template if we want per-locale share text in the future.
* fix(landing-page): preview clicks the thumb; CTA goes to releases
Two follow-ups to #2679 against Joey's review.
1. Preview UX: the thumb is the trigger
The previous shape rendered a static thumb followed by a separate
"View interactive preview ▸" disclosure row underneath. Joey wanted
one composed unit: click the thumb itself to open the live frame.
Wraps the existing `<details>` so that `<summary>` IS the thumb
image (with a hover overlay revealing "Click for live preview ↗"),
and once open the summary hides so the iframe lands in the same
visual slot. The figcaption moves below the open/closed unit so it
labels both states identically.
2. "Use this skill" / "Use this template" → /releases
Sends users straight to the desktop-app release page rather than
pivoting through /quickstart/. The flow is now concrete (download
the binary now) instead of asking users to read an install doc as
step 0. Once the desktop client registers a `od://skill/<slug>`
protocol handler, this anchor flips to a JS try-deep-link-then-
fallback without changing the page surface.
Note on the other two issues Joey raised:
- example.html 404: production has all 4 example files at HTTP 200
(verified with curl). The 404 in his screenshot was production
serving the previous deploy that pre-dates this PR; the fix is in
flight, not a missing route. Once #2679 deploys, the iframe will
resolve cleanly.
- Empty share copy: same root cause. Production HTML still rendered
the pre-#2679 share row (no copy at all). Local dev confirms the
X intent URL contains the full "I'm using <skill> from
@opendesignai — the open-source Claude Design alternative…"
string in the `text` param; Reddit submit URL contains the
"<skill> — open-source Claude Design alternative" title; Email
mailto: subject and body are wired. LinkedIn and Facebook are
URL-only by their vendor design — those platforms read the OG
meta tags from the destination page itself.
Surface area
- Marketing site only. `pages/skills/[slug].astro`,
`pages/templates/[slug].astro`, `sub-pages.css`.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI.
- No new dependencies.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: skill detail thumb shows the live-preview overlay on
hover; click opens the iframe in the same frame. Use this skill
→ opens https://github.com/nexu-io/open-design/releases. Same on
the templates detail page.
* fix(landing-page): example.html copy step + share dialog with copy-then-paste flow
Two follow-ups against Joey's review of #2679.
1. example.html 404 — production was SPA-falling back to the homepage
The "404" Joey screenshotted on
`/skills/deck-guizang-editorial/example.html` was a Cloudflare Pages
SPA fallback: the URL returned HTTP 200 but the body was the
homepage HTML, so the iframe loaded "the homepage inside the iframe"
which the browser displays as broken-page. Root cause: the build
artifact never contained `out/skills/<slug>/example.html`. Astro
generates `<slug>/index.html` for the detail page from `[slug].astro`,
but the canonical `example.html` next to the SKILL.md file in the
repo root never gets copied into `out/`.
Adds `scripts/copy-example-html.ts` and chains it into the
`build` script. After `astro build`, the script walks:
- `skills/<slug>/example.html` → `out/skills/<slug>/example.html`
- `design-templates/<slug>/example.html` → `out/skills/<slug>/example.html`
(design-templates surface as skill-template-origin records in the
catalog and the iframe targets the `/skills/<slug>/example.html`
path for those.)
- `templates/live-artifacts/<slug>/template.html` → `out/templates/<slug>/template.html`
(live-artifact-origin records — the iframe targets template.html.)
Source files that don't exist are silently skipped. The script
prints a summary line so the build log makes the count visible.
2. Share UX — modal with copy-then-paste flow
The previous inline 6-button row had two problems Joey called out:
- Position was below the meta block, not prominent enough.
- LinkedIn and Facebook ignore `text` pre-fill params, so users
landing on those platforms saw an empty composer with no idea
what to write. X / Reddit pre-fill works but truncates Chinese
unpredictably.
Replaces the row with a `<dialog>` modal:
- A `Share ↗` button sits inside `.detail-actions` next to the
primary CTAs, so it has equal visual weight.
- Clicking opens the dialog with the canonical share copy
(containing the brand SEO keyword "open-source Claude Design
alternative") in a readonly `<textarea>`.
- `Copy text` button writes the textarea contents to the clipboard
(with a `prompt()` fallback for older browsers) and flashes the
coral confirmation state.
- `Copy link only` writes just the URL.
- Below: a row of platform jump buttons (X · LinkedIn · Reddit ·
Facebook · Email). Each opens the vendor's compose URL in a new
tab. The user pastes the already-copied text — uniformly
reliable across every platform.
- Modal closes via the × button (form method="dialog") or Escape.
Native `<dialog>` element + `showModal()` API. No new dependencies;
the JS handler lives in the existing `header-enhancer.astro`
inline script alongside the headroom + stars + hamburger handlers.
Surface area
- Marketing site only. `pages/skills/[slug].astro`,
`pages/templates/[slug].astro`, `_components/header-enhancer.astro`,
`sub-pages.css`, plus the new `scripts/copy-example-html.ts` and
one-line `package.json` build script change.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI.
- No new dependencies.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: `/skills/<slug>/` shows the `Share ↗` trigger inside
detail-actions; clicking opens the modal with the readonly
textarea pre-filled with the canonical share copy. Copy text /
Copy link only both flash coral on click and write to clipboard.
Platform buttons open compose pages in new tabs.
- After deploy: `/skills/<slug>/example.html` will resolve to the
actual canonical example output rather than SPA-falling back to
the homepage. Same for templates.
* fix(landing-page): example.html endpoint routes + locale-aware share + brand logos
Three follow-ups against Joey's review of #2679 round 2.
1. example.html 404 — root cause + proper fix
The 404 Joey kept seeing was real, not a deploy lag: nothing in
the build pipeline copied `skills/<slug>/example.html` from the
repo root into the landing-page output. Astro generated only the
detail-page `index.html`; Cloudflare Pages SPA-fell-back to the
homepage on requests for `example.html`, which the browser
rendered as "wrong page in iframe" and Joey read as 404.
Replaces the post-build copy script (`scripts/copy-example-html.ts`,
removed) with two Astro endpoint routes:
- `pages/skills/[slug]/example.html.ts` — streams the canonical
example for skill-template-origin records, including the
design-templates passthrough
(`design-templates/<slug>/example.html` → same URL).
- `pages/templates/[slug]/template.html.ts` — streams the canonical
artifact for live-artifact-origin templates.
Both use `getStaticPaths` so Astro pre-renders into the static
build artifact under `out/`. Works in dev (Astro dev server runs
the endpoint live) and prod (file is on disk after `astro build`).
Required moving `pages/skills/[slug].astro` →
`pages/skills/[slug]/index.astro` (and same for templates) because
Astro can't have BOTH a `[slug].astro` file AND a `[slug]/`
directory with dynamic param children at the same level. The
`[locale]/skills/[slug].astro` re-exporters were updated to point
at the new index files.
`trailingSlash: 'always'` rewrites endpoint URLs to `path/`, so the
iframe `src` and "Open in new tab" anchor now use
`example.html/` and `template.html/` (with trailing slash). Tested
locally: HTTP 200 + real example HTML in the body.
2. Share copy now per-locale; description dropped
The previous template hardcoded the framing in English ("I'm using
X from @opendesignai…") with the description following from
`skill.description`. Joey's catch: when the SKILL.md description is
in one language and the page locale is another, the share text
reads as a forced bilingual mash-up.
Adds an inline `SHARE_COPY` table per landing locale (18 entries,
one per locale). Drops the description from the share template
entirely — the framing + URL is enough to prompt a click, and
removes any chance of a bilingual mismatch when SKILL.md
frontmatter happens to be in a non-matching language.
The brand keyword "open-source Claude Design alternative" stays
English because that's the canonical search query Google
associates with the domain — translating it would split the
entity claim. Surrounding sentence translates per locale so the
message reads as one voice.
Same template added for templates/[slug]/index.astro.
3. Share dialog UI: brand logos for the 4 platform jump buttons; Email dropped
Replaces the previous text labels (`X` / `LinkedIn` / `Reddit` /
`Facebook` / `Email`) with inline-SVG brand logos. Per Joey's
revision the Email channel was dropped — Gmail / Outlook
pre-fill is reliable but the audience reach is much smaller than
the four social platforms, and removing it tightens the row.
Logos are SimpleIcons-style SVG paths inlined directly (no font
dependency, no external icon library). Each button keeps an
`aria-label` plus a visually-hidden `<span class="sr-only">`
for screen readers.
Surface area
- Marketing site only. `pages/skills/[slug]/index.astro`,
`pages/skills/[slug]/example.html.ts`,
`pages/templates/[slug]/index.astro`,
`pages/templates/[slug]/template.html.ts`,
`_components/header-enhancer.astro`, `sub-pages.css`,
`package.json` (build script revert), and the two `[locale]/...`
re-exporters.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces.
- No new top-level dependencies.
- The two restructured detail pages keep their existing route URLs
and existing static-paths logic — only the file location changed.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: `/skills/deck-guizang-editorial/example.html/` returns
HTTP 200 with a 4942-byte body that's the actual canonical
example output (not the homepage SPA fallback).
- Local dev: `/skills/deck-swiss-international/` share dialog shows
4 brand-logo platform buttons (no Email); textarea contains the
English-only framing + URL. `/zh/skills/...` shows the Chinese
framing + URL with no English bleed-through.
* fix(landing-page): punchier share copy with emojis across 18 locales
The previous share template ("I'm using <name> from @opendesignai —
the open-source Claude Design alternative.\n\n<url>") was too flat to
spark a click — Joey called it out as 平淡 with the keyword
front-and-center but no hook.
New shape: three-line punchy block with emojis as visual anchors.
Skills surface (`/skills/<slug>/`):
🎨 Just discovered <name> on @opendesignai — the open-source
Claude Design alternative.
✨ Local-first · BYOK · your agent does the design.
→ <url>
Templates surface (`/templates/<slug>/`):
🎨 Just forked <name> from @opendesignai — the open-source
Claude Design alternative.
✨ Templates as files, not vendor docs. Fork → swap → ship.
→ <url>
Pattern per locale:
- Line 1: action verb hook (`Just discovered` / `Just forked` /
locale equivalent like `安利一个` / `推薦一個` / `Gerade entdeckt` /
`Découvert` / etc) + skill name + brand keyword.
- Line 2: tight value-prop with `·` separators — Local-first ·
BYOK · agent does the design (skills) or Templates as files,
not vendor docs (templates).
- Line 3: → URL.
Both lines lead with an emoji (🎨 then ✨) so the post visually pops
in a feed. The brand keyword "open-source Claude Design alternative"
stays English in every locale (canonical search query for the
domain); surrounding sentence translates per locale.
All 18 landing locales rewritten — ar, de, en, es, fr, id, it, ja,
ko, nl, pl, pt-br, ru, tr, uk, vi, zh, zh-tw. Skills and templates
each have their own `SHARE_COPY` table; the templates variant has
fork-flavored framing because the user action there is fork-and-ship,
not run-once.
Surface area
- Marketing site only. `pages/skills/[slug]/index.astro` and
`pages/templates/[slug]/index.astro`.
- No other files touched. No new dependencies.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors
- Local dev: en / zh / ja all render with emojis intact and
language-specific framing; X intent URL preserves the multiline
breaks via `\n` in the `text` query param.
* fix(landing-page): restore post-build copy step for preview iframes
The detail-page interactive preview iframe pointed at endpoint routes
(`pages/skills/[slug]/example.html.ts`,
`pages/templates/[slug]/template.html.ts`) introduced in
|
||
|
|
586e2a0c3b |
Fix Draw Send to chat during streaming (#1961)
* Fix queued draw annotation send while streaming * Wire draw send while streaming |
||
|
|
f08f7e390b |
fix(daemon): strip stale BYOK OPENAI_API_KEY for codex spawn (#2441)
Mirror the issue #398 fix the claude adapter already has: when spawning Codex CLI without a custom OPENAI_BASE_URL, strip both OPENAI_API_KEY and CODEX_API_KEY from the child env so Codex CLI's own `~/.codex/auth.json` (codex login) wins. Without this guard, a stale BYOK key left behind in `agentCliEnv.codex.OPENAI_API_KEY` (e.g. after the user clears the BYOK dialog and switches execution mode back to Local CLI) silently flows through `spawnEnvForAgent` and trips 401 invalid_api_key. The stripping is gated on OPENAI_BASE_URL so users who intentionally route Codex CLI through a third-party OpenAI-compatible gateway keep the credential that authenticates against it. Comparison is case-insensitive to close the Windows mixed-case env name hole that issue #398 already documents for ANTHROPIC_API_KEY. Fixes #2420 |
||
|
|
33b96f184e |
fix(web): compact plugin authoring composer (#2492)
* fix(web): compact plugin authoring composer * fix(web): compact pending plugin authoring composer * fix(web): compact plugin authoring handoff composer * fix(web): align plugin composer layout rebase --------- Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com> |
||
|
|
8eecf665da |
Fix Design Files workspace loading (#2703)
* Fix Design Files workspace loading Generated-By: looper 0.8.1 (runner=worker, agent=codex) * Fix Design Files workspace loading Generated-By: looper 0.8.1 (runner=fixer, agent=codex) * Fix Design Files workspace loading Generated-By: looper 0.8.1 (runner=fixer, agent=codex) |
||
|
|
22e057cc6a |
Reject reserved project paths in HTML artifacts (#2484)
* Reject reserved paths in HTML artifacts * Handle reserved artifact path edge cases --------- Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com> |
||
|
|
48ed23c72f |
fix(daemon): finish live-artifact chat runs via watchdog quiet-period handoff (#1451) (#2585)
* fix(daemon): finish live-artifact chat runs via watchdog quiet-period handoff (#1451) Live-artifact runs were staying in `Working` for the full 10-minute inactivity window even after the deliverable had been registered, and sometimes finishing as `failed` with `Agent stalled without emitting any new output for 600s`. The agent process kept its stdin/stdout alive (claude-code stream-json idle stdin, post-write reasoning that never reaches the chat) so the existing watchdog could not tell the deliverable was already in the user's hands. Wire `/api/tools/live-artifacts/create` back into the chat run via a small per-run handle registry: on the first `created` event, the run flips a local `artifactRegistered` flag and rearms the watchdog with the shorter `OD_CHAT_RUN_ARTIFACT_QUIET_PERIOD_MS` (default 60s) instead of the 10-minute pre-artifact ceiling. When that quiet timer trips, the watchdog no longer emits a stalled-error / `failed` finish; it SIGTERMs the child and lets the existing child-exit handler do final classification — the close handler now treats a SIGTERM exit after a registered artifact as `succeeded`, matching what the user actually got (a delivered artifact, not a failed run). The handoff stays with the existing child-exit lifecycle, so tool token revocation, cancel semantics, and exit-status classification keep their current owner — addressing the PR #1543 review history where finishing the run from the tool route bypassed those guarantees. Closes #1451. * fix(daemon): gate artifact quiet-period close on daemon-initiated flag (#1451 review follow-up) Reviewer (#2585) found that the close-handler branch reclassifying SIGTERM/SIGKILL as `succeeded` only checked `artifactRegistered`, so an unrelated later termination (external `kill`, OOM, container shutdown) after a successful artifact write would silently flip the run from `failed` to `succeeded` — the exact "completed without producing anything visible" failure mode the existing close handler is trying to prevent. Track the watchdog-initiated shutdown explicitly: set `artifactQuietShutdownRequested = true` immediately before `failForInactivity()` sends SIGTERM (covering the kill-grace SIGKILL escalation under the same flag), and require that flag in the close handler's quiet-period branch. Extract the final-status decision into a pure `classifyChatRunCloseStatus` so the daemon-initiated vs external signal cases can be pinned with focused unit tests instead of asserting closure-internal state via end-to-end timing. * fix(daemon): treat OD_CHAT_RUN_ARTIFACT_QUIET_PERIOD_MS=0 as disabled (#1451 review follow-up) Reviewer (#2585 non-blocking) found that an operator override of `OD_CHAT_RUN_ARTIFACT_QUIET_PERIOD_MS=0` no longer behaved as a "disable the quiet period" knob: once the artifact was registered, `activeInactivityTimeoutMs()` dropped to 0, `noteAgentActivity()` early-returned without clearing the prior timer, and the pre-artifact 10-minute timer kept running while further agent activity stopped refreshing it. Make the quiet-period switch conditional on a positive value. A 0 override now means "do not shorten after artifact registration" — the pre-artifact ceiling stays active, subsequent activity continues to reschedule it, and the existing pre-artifact stalled-error path still fires when the agent genuinely hangs. Pin the resolver as a pure `resolveActiveInactivityTimeoutMs` helper so the four quiet-vs-pre matrix cases are unit-tested directly. * fix(daemon): arm the quiet-period watchdog when pre-artifact timeout is disabled (#1451 review follow-up) Reviewer (#2585 non-blocking, round 3) found that `OD_CHAT_RUN_INACTIVITY_TIMEOUT_MS=0` paired with `OD_CHAT_RUN_ARTIFACT_QUIET_PERIOD_MS>0` left the watchdog disarmed forever. The `noteAgentActivity()` call at run start exited early because the pre-artifact delay was 0, so `inactivityTimer` was still `null` when the artifact was registered, and the prior `if (inactivityTimer) noteAgentActivity()` guard inside `noteArtifactRegistered()` then skipped the re-arm. The newly-positive quiet-period delay never armed a timer at all — a chat run that went silent right after artifact creation would stay `running` indefinitely. Drop the guard. `noteAgentActivity()` is already the function that decides whether to schedule (it bails when the active delay is 0), so calling it unconditionally keeps the behavior coherent across the four pre/quiet combinations: both non-zero (was already fine), pre=0 + quiet>0 (now arms the quiet timer), pre>0 + quiet=0 (still falls back to the pre-artifact ceiling via the existing resolver), both zero (still no watchdog at all — operator opted out). Pure-function coverage of the ceiling decision stays in `resolveActiveInactivityTimeoutMs` — exercised across the same four combinations in the existing unit suite. |
||
|
|
53ecd36b9d |
fix: preview close button hover (#2671)
* fix: preview close button hover * fix: restore preview close button visibility on hybrid devices |
||
|
|
1b908a8481 |
fix(daemon): restore full assistant turn after mid-flight reload reattach (#2383)
* fix(daemon): restore full assistant turn after mid-flight reload reattach When a daemon run is in progress and the browser reloads, the client reattaches and the artifact recovers, but the restored chat turn drops assistant text, thinking events, and producedFiles. Three independent defects combine to cause this: 1. The reattach onDone never populated producedFiles. The pre-turn file snapshot used as the diff baseline lived only in a closure. Now it is persisted on the assistant message as preTurnFileNames so the reattach path can rebuild the diff after reload. 2. The SSE replay used a strict `>` cursor compare. A client that had already persisted lastRunEventId equal to the final event id received zero replay events on terminal-run reattach, fell into the status-only REST fallback, and never fired a clean onDone. The server now replays the final buffered event on terminal-run reattach when the cursor is at or past the end, so the client always sees a terminal signal. 3. The text buffer flushed on visibilitychange but not on pagehide. Hard reloads on browsers where visibilitychange does not fire before teardown could lose the last ~250ms of streamed text from the persisted message. A pagehide listener now flushes synchronously. Refactor: extracted computeProducedFiles helper so the send and reattach flows share the diff logic and cannot drift apart again. Tests: - apps/web/tests/components/ProjectView.reattach-restore.test.tsx covers: reattach onDone populates producedFiles from preTurnFileNames; reattach reaches succeeded via SSE even when only the end event replays; computeProducedFiles unit cases. - apps/daemon/tests/runs.test.ts adds replay-cursor coverage for both the terminal-replay safety branch and the no-duplicate normal branch. * fix(daemon): persist preTurnFileNames end-to-end on the messages table Review on #2383 caught that `ChatMessage.preTurnFileNames` (added in packages/contracts) had no daemon-side persistence: the messages schema, upsertMessage, and normalizeMessage all ignored the field. saveMessage() would PUT the field, the daemon would silently drop it, and a real page reload would read a row without `preTurnFileNames`, so the reattach onDone fell back to `new Set(nextFiles.map(...))` and still missed files produced earlier in the turn. This commit closes the round trip: - New `pre_turn_file_names_json TEXT` column on the messages table, with a forward-compatible ALTER for existing databases (same pattern as agent_id / feedback_json / run_status). - Both upsertMessage branches (UPDATE and INSERT) now serialize m.preTurnFileNames into the new column. - listMessages, the post-upsert readback SELECT, and normalizeMessage surface the column back to callers. Round-trip tests in apps/daemon/tests/db-pre-turn-file-names.test.ts cover: write+listMessages, the UPDATE upsert path preserving the baseline, and a legacy-row case returning undefined. * fix(web): preserve terminal status + full multi-file diff on reattach Two correctness issues caught in review of the prior reattach commits: 1. The reattach onDone path hard-coded `runStatus: 'succeeded'`, which overwrote a 'failed' or 'canceled' status that the replayed terminal event had already recorded via onRunStatus. Restored messages would come back as success even when the run had actually failed or been canceled. Now derives the final status from `prev.runStatus` via the existing `resolveSucceededRunStatus` helper, mirroring the send path at line 2333. 2. When `findExistingArtifactProjectFile()` recovered an existing on-disk artifact, the produced-files list was replaced with that single file, dropping any other files the turn had created earlier. Now always computes the full diff against `preTurnFileNames`, then appends the recovered artifact only if it isn't already in that set. Extracted as `mergeRecoveredArtifact(diff, recovered)` so the logic is a unit-testable invariant. Tests in ProjectView.reattach-restore.test.tsx: - mergeRecoveredArtifact: three cases (recovered appended to pre-files, no duplication when already in the diff, passthrough on no recovery). - reattach failed-status: onRunStatus('failed') → onDone → final saveMessage has runStatus 'failed', not 'succeeded'. - reattach canceled-status: same shape for cancellation. * fix(web): force keepalive PUT on pagehide so the last buffered chunk survives reload Review on #2383 caught that onPageHide() only called flush(), which updates React state then schedules persistSoon() — a 500ms debounce. On a hard reload the page tears down before that timer fires, so the final ~250ms of streamed text never reaches the daemon. Threaded a new flushAndPersistNow() callback through createBufferedTextUpdates(). Both buffer call sites (send-path + reattach-path) supply it backed by persistMessageById(id, { keepalive: true }). saveMessage in state/projects.ts forwards the new SaveMessageOptions.keepalive flag onto fetch's keepalive option, which the browser honors specifically for unload-time requests. onPageHide now calls flush() followed by flushAndPersistNow?.(), so: - flush() pushes the buffered delta into React state synchronously - the immediate persistMessageById then PUTs the updated message with keepalive:true, surviving document teardown Regression test in ProjectView.reattach-restore.test.tsx: stream a delta, dispatch pagehide, assert saveMessage was called with the flushed content AND { keepalive: true } before the 500ms debounce would otherwise have fired. |
||
|
|
f799fbd7ed |
i18n(zh-CN): translate Manual editor and Cloudflare deploy panels (#2242)
Fill in 68 previously-untranslated strings in apps/web/src/i18n/locales/zh-CN.ts: - Manual editor panel (manualEdit.*): 42 keys covering layer list, tabs (Content / Style / Attributes / HTML / Source), apply buttons, undo/redo, and the style editor fields (Font size, Padding, Margin, Radius, Border, etc.). - File viewer viewport (fileViewer.viewport*): Desktop / Tablet / Mobile labels and title tooltips. - Cloudflare deploy panel (fileViewer.cloudflare*): domain picker, loading / refresh / empty / failed states, subdomain prefix label and validation, custom-domain hint and preview, Vercel token label. - Misc: connectors load-more / unavailable copy, designs Live / Slide / Media tags, orbit live-artifact kicker. Brand names (Open Design, Cloudflare Pages, pages.dev), technical acronyms (CLI, API, BYOK, MCP, PDF), product concepts (Orbit, Star), and placeholder strings (notes.txt, demo, /path/to/skill-folder) are intentionally kept in English per TRANSLATIONS.md. Co-authored-by: zhongrenfei1-hub <231221504+zhongrenfei1-hub@users.noreply.github.com> |
||
|
|
00b3f3e52d |
feat(design-files): add directory navigation and localization for folders (#2442)
* feat(design-files): add directory support and localization for folders in design files panel * feat(directory-navigation): implement directory navigation and selection reset in DesignFilesPanel * feat(rename): improve draft handling for file renaming in DesignFilesPanel |
||
|
|
1fd50197dd |
perf(landing): route HTML Anything screenshots through R2 + Image Resizing (#2696)
The `/html-anything/` landing page was loading 11 screenshots directly
from `raw.githubusercontent.com/nexu-io/html-anything/main/docs/…` —
that's 6.5 MB of raw PNGs per first visit, no AVIF/WebP transcoding,
and a separate cross-origin TLS handshake the rest of the site doesn't
pay. The TODO comment at line 70 of the page has been calling this out
since the page landed.
This PR routes those images through the same pipeline as `hero.png`:
- Source PNGs upload to `static.open-design.ai/landing/assets/html-anything/`
on the existing `open-design-static` R2 bucket. Upload package lives
at `~/Downloads/r2-html-anything-orig/` (README + upload.sh ready);
merge AFTER running that script.
- Page uses `imageAsset('html-anything/<name>.png', { width, quality })`
so CF Image Resizing emits `format=auto` AVIF/WebP variants — same
contract every other landing image already uses.
- Banner is the LCP element; gets `srcset` across 768/1280/1920/2400
(mirroring `heroImageSrcset`) plus its existing `fetchpriority="high"`.
- 10 below-the-fold screenshots become `<LazyImg>` so they pick up the
precise IntersectionObserver (rootMargin 300px) instead of Chrome's
native lazy load that over-prefetches up to 3000px.
Expected on first visit (mobile, AVIF-capable browser):
banner: ~150 KB AVIF (was 2.05 MB PNG)
10 screenshots ~80-120 KB each (was 500-900 KB PNG each)
total ~1.2-1.5 MB (was 6.5 MB)
LCP estimated -800ms to -1.5s
|
||
|
|
3e2f037730 |
feat(daemon): add CTA hierarchy static QA pass (refs #2251) (#2427)
* feat(daemon): add CTA hierarchy static QA pass Introduce apps/daemon/src/qa/cta-hierarchy.ts exporting a pure analyseCtaHierarchy(html) that parses generated prototypes with cheerio and flags three precision-biased findings: multiple-primary CTAs in the same section, ambiguous-weight (all CTAs share identical class + inline style), and misleading-prominence (secondary-coded copy like "Learn more" / "了解更多" styled with primary weight). CTA candidates come from <button>, <a>, role="button" with btn/button/cta class markers plus CTA copy keywords covering both English (Get started, Sign up, Buy, Subscribe, Learn more, ...) and Chinese (立即购买, 立即下单, 了解更多, ...). Weight is inferred from class tokens (primary/solid/filled/accent/cta) and from non-transparent inline background-color, matching the inverse of the issue #2251 sample where the header CTA was rendered with the neutral .btn style. This PR only ships the pure function plus its tests. HTTP route, CLI subcommand, and any auto-repair feedback loop are deliberate follow-ups so the first cut can land without touching the daemon HTTP surface. Refs #2251 * fix(qa): respect container boundaries in CTA hierarchy heuristics Two precision fixes from review of #2427: - computeContainerKey()'s parent fallback keyed by tag name alone, so flat layouts like <div><a class=btn-primary>...</a></div> repeated for sibling cards all landed in 'parent:div' and detectMultiplePrimary() reported a fake shared-section conflict on what is in fact one CTA per card. Switch to parent-node identity (positional index of the matched parent within its tag group, same trick the landmark branch already uses), so each sibling wrapper gets its own bucket. - detectAmbiguousWeight() compared signatures across the entire document, so two unrelated sections each containing one '.btn' CTA with matching style would trigger 'ambiguous-weight' despite neither container having 2+ CTAs. The PR body's rule is narrower — 'every CTA in a container shares the same class + inline style' — so bucket by containerKey first and only emit the finding for containers with 2+ CTAs whose signatures are identical. Tests lock both behaviors down: - sibling <div> card-grid without a landmark ancestor stays under the multiple-primary threshold; - one-CTA-per-section pairs stay under the ambiguous-weight threshold. |
||
|
|
3743c65e6e |
fix(web): allow configured dev origins (#2216)
Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
ca8de8bd0b |
fix(web): localize plugin card actions for Chinese locale (#2424)
PluginCard 中 Details/Use/Use with query/Applying…/Publish/Contribute/ Starting… 等按钮文本及 aria-label/title 此前为硬编码英文,导致中文 locale 下仍显示英文。新增 pluginCard.* 命名空间下 15 个 i18n key, 补全 zh-CN 简体与 zh-TW 繁体翻译;其他 16 个 locale 走 ...en 字典 fallback,保留既有显示行为。 Fixes #2079 |
||
|
|
c5e38bbe58 |
fix(prompts): remove 10-item cap from discovery TodoWrite plan (#2298)
* fix(daemon): remove 10-item cap from discovery TodoWrite plan prompt The RULE 3 sentence in DISCOVERY_AND_PHILOSOPHY told the model to write 'a plan of 5–10 short imperative items'. That upper bound caused the agent to cap every plan at exactly ten steps even when the task genuinely needed more. The TodoWrite JSON schema imposes no maxItems constraint, so the cap was entirely prompt-driven. Replace '5–10 short imperative items' with 'short imperative items covering the work'. TodoWrite intent, RULE 3 label, and planning-before-building requirement all survive unchanged. Red spec: apps/daemon/tests/prompts/discovery-todo-cap.test.ts * fix(prompts): remove 10-item cap from contracts discovery copy and harden tests [pass-6,7 BLOCKER] packages/contracts/src/prompts/discovery.ts still had the old '5-10 short imperative items' wording. apps/web imports composeSystemPrompt from @open-design/contracts (ProjectView.tsx:43), so web-originated chat runs were still subject to the cap. [pass-8 WARNING] discovery-todo-cap.test.ts did not cover the contracts copy, leaving that path unguarded. Also no guard against semantically equivalent re-introduction via 'at most / maximum / no more than'. Changes: - packages/contracts/src/prompts/discovery.ts: apply same wording fix as apps/daemon; add inline rationale comment - apps/daemon/src/prompts/discovery.ts: add inline rationale comment - apps/daemon/tests/prompts/discovery-todo-cap.test.ts: add 4th assertion blocking 'at most|maximum|no more than N item' re-introduction - packages/contracts/tests/system-prompt.test.ts: add 5-assertion suite guarding the contracts copy and composed prompt output |
||
|
|
f183f937c0 | fix: improve long project name rendering (#2258) | ||
|
|
45c77fac41 |
fix(web): restore manual source edit tabs (#1854)
* fix(web): restore manual source edit tabs * test: target active manual edit preview |
||
|
|
6273874e30 |
fix codex stream reconnect handling (#2274)
Co-authored-by: Sumit Dhawan <sumitdhawan@Sumits-MacBook-Air.local> |
||
|
|
4cd93a54dd | fix(web): Enter to send in project chat composer (#2676) | ||
|
|
7fd0169888 |
fix(web): keep trailing punctuation out of bare chat URLs (#2678)
* fix(web): keep trailing punctuation out of bare chat URLs (#2675) * test(web): broaden bare URL punctuation regression coverage Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * test(web): broaden bare URL punctuation regression coverage Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * test(e2e): harden smoke suite tools-dev port startup Generated-By: looper 0.6.0 (runner=fixer, agent=codex) |
||
|
|
aa0616062d |
refactor(daemon): introduce HTTP Request Adapter + typed Deps (#2636)
* refactor(daemon): introduce HTTP Request Adapter + typed Deps (proof on active-context-routes)
Adds a typed HTTP boundary Adapter under apps/daemon/src/http/ that replaces
the untyped ServerContext service-locator pattern (30+ fields, mostly any)
for route handlers. Routes become pure (input, deps) -> Result<output>
functions, unit-testable without Express or supertest.
Six new modules under apps/daemon/src/http/:
- types.ts Result<T,E>, ok(), err(), JsonRouteSpec, Handler,
RouteInputContext, HttpMethod, InputParser
- parse.ts rawInput(req), validationError(message, issues?)
- response.ts sendJson(), sendApiError(), statusForError() +
ERROR_STATUS_BY_CODE map
- origin-guard.ts guardSameOrigin(req, origin) wrapping isLocalSameOrigin
as a Result
- adapter.ts defineJsonRoute(), mountJsonRoute() (only place that
knows about req/res)
- index.ts barrel
active-context-routes.ts migrated as proof of pattern. parsePostActive(),
handlePostActive(), handleGetActive() are now pure functions; postActiveRoute
and getActiveRoute are exported route specs. The wire signature
registerActiveContextRoutes(app, ctx) is preserved so server.ts is untouched.
Spec at specs/current/daemon-http-adapter.md captures the strangler migration
order for the remaining route files (mcp-routes, chat-routes, artifact
routes, etc.) and a StreamRoute follow-up where the Run Orchestrator lands.
Wire-format note: cross-origin response moves from the legacy
{ error: 'cross-origin request rejected' } shape to the structured
{ error: { code: 'FORBIDDEN', message: ... } } shape. Backwards-compatible
via the existing CompatibleErrorResponse = ApiErrorResponse | LegacyErrorResponse
union in @open-design/contracts.
Validation:
- pnpm install (post-rebase, exit 0)
- pnpm --filter @open-design/daemon typecheck (both tsconfig.json and
tsconfig.tests.json silent => pass)
- pnpm --filter @open-design/daemon test: 15 new tests pass
(tests/http/adapter.test.ts + tests/active-context-routes.test.ts).
84 pre-existing failures across 23 files are unchanged and unrelated
to this PR (Windows symlink / short-name / colon-in-filename, upstream
behavior drift, missing plugin marketplace fixtures, and a freshly-
added tools-connectors-cli suite of 38 failures that landed during
the rebase).
Sharpens W4/W5 of specs/current/maintainability-roadmap.md and unlocks
W6 (Run Orchestrator).
* chore: add core-js, electron-winstaller, protobufjs, sharp to pnpm.onlyBuiltDependencies
|
||
|
|
558fedd207 |
fix(landing): wire GA4 rollout config (#2615)
Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
5f7d65d513 |
perf(landing): preconnect api.github.com + rAF-throttle scroll listener (#2666)
Two PSI-targeted wins (split from #2599 follow-up). 1. New `resource-hints.astro` mounted in every page's <head> declares `<link rel="preconnect" href="https://api.github.com" crossorigin>`. The inline enhancer script on /` issues 3 fetch() calls to api.github.com right after DOMContentLoaded (stars, latest release, contributors). Without preconnect each pays a full DNS + TCP + TLS handshake (~150-300ms) inline with the fetch. With preconnect those handshakes happen in parallel with HTML parse and all three share one warmed HTTP/2 connection. 2. Wrap the scroll listener's read + classList write in requestAnimationFrame. Trackpads and high-rate wheels fire scroll faster than display refresh, and every callback that hits classList triggers layout recalc. PSI was attributing ~700ms of "forced reflow" to the un-throttled version. The rAF gate collapses each burst to one DOM mutation per frame; `{ passive: true }` is preserved so the listener still doesn't block the scroll thread. Same throttling pattern mirrored to `header-enhancer.astro` (used by every sub-page) and `home-enhancer.astro` (kept in lockstep even though /` currently uses its own inline copy). Expected PSI delta: - "Preconnect to required origins" hint: cleared - "Forced reflow" diagnostic 700ms → near zero - LCP: small bonus from earlier GH fetch warm-up (~100-300ms) |
||
|
|
cc6edb9afe |
Proxy GitHub metadata through the daemon (#2654)
* Proxy GitHub metadata through the daemon * fix(contracts): share GitHub metadata responses Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * fix(contracts): align GitHub fetchedAt payload types Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * Proxy GitHub metadata through the daemon Generated-By: looper 0.6.0 (runner=fixer, agent=codex) |
||
|
|
83c4ef979a | Polish onboarding dropdown menu (#2601) | ||
|
|
bddec2391d | Fix Simple Deck discovery form contract (#2602) | ||
|
|
63c8dd1fed |
fix(web): localize the Routines settings page (#2598)
The Settings → Automations/Routines section (RoutinesSection.tsx) used hardcoded English for every label, action, status pill, schedule description, time suffix, and dialog. With the app language set to Chinese the whole section stayed in English, breaking the localized experience. Introduce a dedicated `routines.*` i18n namespace, wire RoutinesSection and the SettingsDialog routines header through `useT()`, and add Simplified + Traditional Chinese translations. Schedule summaries are now built from interpolated keys instead of concatenated English literals, and the 12-hour AM/PM suffix is localized too. The remaining locales fall back to English via the existing `...en` spread, matching the repo's incremental per-locale translation pattern. A regression guard in locales.test.ts asserts the representative `routines.*` keys are actually translated (not English fallback) in zh-CN and zh-TW. |
||
|
|
bfdd029d7d |
feat(web): polish automation schedule picker and summary pill UI (#2593)
* feat(web): polish automation schedule picker and summary pill UI * refactor(web): share automation schedule summary formatting * fix(web): scope automation pill flex layout to schedule segments |
||
|
|
575d5aaf2f | fix(web): strip trailing punctuation from markdown link URLs (#2568) (#2591) | ||
|
|
5b53c44e13 |
fix: enable SenseAudio BYOK TTS (#2570)
* fix: enable SenseAudio BYOK TTS * fix: handle BYOK SenseAudio TTS failures * fix: harden SenseAudio BYOK TTS responses * fix: preserve BYOK speech tool failure kind * fix: handle versioned SenseAudio TTS base URLs |
||
|
|
7f03030f3f |
perf(landing): self-host fonts + inline critical CSS (#2599)
* perf(landing): self-host fonts + inline critical CSS
PageSpeed Insights flagged ~2.3s of render-blocking on /:
globals.css 12.9 KB external link, 160ms
fonts CSS 2.2 KB fonts.googleapis.com, 750ms
+ 4 woff2 ~1200ms each from fonts.gstatic.com
Two changes drop that whole chain:
1. Self-host fonts via @fontsource-variable/{inter,inter-tight,
playfair-display,jetbrains-mono}. Each family ships a single variable
woff2 (covers all weights we use) that Astro bundles into /_astro/*
alongside the rest of the build, served same-origin through CF Pages —
no separate TLS handshake, no Google Fonts CSS round-trip. The CSS
variable names get an extra alias in front (`'Inter Tight Variable',
'Inter Tight', ...`) so a system fallback still works if the package
ever ships under a different family name.
2. `astro.config.ts: build.inlineStylesheets: 'always'` inlines every
emitted <style> into the HTML <head> instead of emitting a separate
/_astro/*.css link. The HTML grows from ~13KB to ~28KB (gzip) but
loses one stylesheet round-trip + the entire @font-face chain that
used to gate text rendering.
Component cleanup: the `<FontStylesheet>` component (preconnect + link to
fonts.googleapis.com) is no longer needed and is deleted, removed from
all 7 places that mounted it. og.astro keeps its own font setup since
it renders to a screenshot.
Expected effect (from PageSpeed Insights "Render-blocking requests"
diagnostic on the previous build):
FCP 1.9s → ~1.2s
LCP 2.2s → ~1.5s
Verified: pnpm typecheck 0 errors, pnpm build 1853 pages 78s, preview
serves /_astro/*.woff2 as font/woff2 same-origin, 0 fonts.googleapis or
fonts.gstatic references in the built HTML.
* perf(landing): include Playfair italic + bump nix pnpm-deps hash
Two follow-ups on the self-host fonts PR:
1. globals.css imported only `@fontsource-variable/playfair-display`,
which ships @font-face for font-style: normal only. The previous
Google Fonts URL included the italic axis (`ital,wght@0,500;1,400;
...`) and several rules (.roman, .work-rule .roman, .sec-rule .roman,
plus 8 other italics across globals.css + sub-pages.css) render
Playfair italics via `font-family: var(--serif); font-style: italic`.
Without the italic face self-hosted, those would fall through to
Times New Roman italic or browser synthesis. Adding
`wght-italic.css` keeps the typography visually equivalent.
2. nix/pnpm-deps.nix uses a fixed-output derivation hash that has to
match the pnpm vendored store; adding the four fontsource packages
changed pnpm-lock.yaml so the hash has to be bumped to the value Nix
reported in CI.
Codex (Looper reviewer) flagged #1 as non-blocking.
* perf(landing): pin fontsource versions exactly per repo guard
`pnpm add` defaulted to caret ranges (`^5.2.8`) but repo guard rejects
non-exact specs ("dependency specs must be exact versions like 1.2.3 or
workspace:*"). That was the actual cause of the Preflight + Validate
workspace failures — pinning to the locked versions Codex reviewer
called out:
@fontsource-variable/inter 5.2.8
@fontsource-variable/inter-tight 5.2.7
@fontsource-variable/jetbrains-mono 5.2.8
@fontsource-variable/playfair-display 5.2.8
`pnpm guard` now passes locally (6/6 tests).
|
||
|
|
95bbdbb734 |
[codex] test(e2e): harden settings and entry regressions (#2578)
* test(e2e): harden settings and entry regressions * test(e2e): align entry chrome coverage with current UI Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * fix(web): refresh saved media providers from daemon Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * test(web): align media provider reload expectations Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * fix(web): keep daemon media-provider reloads authoritative Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * fix(web): make media-provider reload precedence depend on dialog edits Generated-By: looper 0.6.0 (runner=fixer, agent=codex) * fix(web): preserve pending media-provider edits across stale autosaves Generated-By: looper 0.6.0 (runner=fixer, agent=codex) |
||
|
|
4cb5778669 |
feat(landing-page): retitle HA page for brand-defense SEO across 18 locales (#2618)
Replaces the HA page `<title>` from the previous descriptive form
("HTML Anything — your local AI agent writes the HTML, you ship
it | Open Design") with a noun-stack that captures three high-value
brand-defense queries:
- "open source html anything" — researching alternatives
- "html anything official" — verifying the canonical source
- "open source html anything official" — precise long tail
Why
- The descriptive title before this change told the story but didn't
match what users actually type when they land on this page from
search. The brand has been growing fast (4.1K+ stars on the repo);
visitors increasingly arrive with intent to verify ("is this the
real one?"), to compare alternatives, or to find the canonical
install path. The new title format leads with the tokens those
queries hit.
- Joey called the change explicitly. The brand has reached a stage
where SEO defense beats descriptive copy on a one-line surface.
What
- New `OPEN_SOURCE_OFFICIAL_TITLE` constant maps every landing-page
locale to its translated title. Pattern: `{open-source} HTML Anything
{official}` with no separator. Word order follows what's natural in
each locale (German: Offiziell at end; French/Romance: adjectives
after the noun; CJK: adjectives before).
- The three explicit copy blocks (`HTML_ANYTHING_COPY_EN`,
`_ZH`, `_ZH_TW`) all reference the constant rather than holding
their own literal title strings, so future title changes update
in one place.
- `derivedHtmlAnythingCopy()` (which builds copy for the 15
non-explicit locales by spreading EN over locale UI strings) now
pulls the title from the same constant via `[locale]` lookup with
EN as a defensive fallback.
- Adds three new `SoftwareApplication.alternateName` entries for the
same query stack. Schema-level signal complements the title-level
signal: the structured-data entity now matches whichever shape the
user typed regardless of UI locale, since these are proper-noun
query variants that don't translate.
Surface area
- Marketing site only. Single file: `app/pages/html-anything/index.astro`.
- No `apps/web`, no `apps/daemon`, no contracts, no CLI.
- No new dependencies.
Validation
- `pnpm --filter @open-design/landing-page typecheck` — 0 errors.
- Local dev: every locale's HA page renders the new title.
- en → `<title>Open Source HTML Anything Official</title>`
- zh → `<title>开源 HTML Anything 官方</title>`
- ja → `<title>オープンソース HTML Anything 公式</title>`
- de → `<title>Open Source HTML Anything Offiziell</title>`
- ar → `<title>HTML Anything مفتوح المصدر الرسمي</title>`
- …same shape across all 18 landing locales.
Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
|
||
|
|
3db1e27b81 |
fix(landing-page): translate Library/Tutorials, drop dropdown numbers, dedupe Product menu (#2610)
* fix(landing-page): translate Library/Tutorials, drop dropdown numbers, dedupe Product menu Three small UX issues surfaced after #2605 landed Tom's full i18n bundle. None of them block deploy; all of them affect every visitor who lands on the site, especially the non-English ones. Issues 1. The new Library / Tutorials nav labels in #2605's header restructure were hardcoded English. On `/zh/`, `/ja/`, `/de/`, etc. the rest of the nav was localized but those two labels stayed "Library" / "Tutorials" inside otherwise-translated chrome. 2. The four catalog facets inside the Library dropdown carried the live count badges from the previous top-row treatment ("Skills132", "设计系统150", etc.). Inside a dropdown they read as a glued-on suffix rather than a tag, and Joey called them out as "weird — drop them entirely". 3. The Product dropdown still listed Tutorials as a child item alongside Open Design and HTML Anything. After #2605 made Tutorials a standalone top-level link, the duplicate was confusing — same URL appearing in two places in the same nav row. 4. Chinese (zh and zh-tw) had `skills: 'Skill'` — the singular English word, untranslated in an otherwise fully Chinese nav. Fixes - Extends `HeaderCopy.nav` interface with `library` and `tutorials` keys; populates both across all 18 landing locales (en, zh, zh-tw, ja, ko, de, fr, ru, es, pt-br, it, vi, pl, id, nl, ar, tr, uk). - Updates `header.tsx` to read `headerCopy.nav.library` and `headerCopy.nav.tutorials` instead of literal strings. - Removes the four `<span class="dropdown-num">{count}</span>` badges from inside the Library dropdown items. The numbers still sit on the catalog index pages themselves (header counts, hero) and on the homepage; the dropdown is for navigation, not status. - Removes the Tutorials `<li>` from the Product dropdown — Tutorials lives at the top-row level only. Updates the Product trigger's `is-active` set so it no longer highlights when on `/tutorials/`. - zh/zh-tw `nav.skills` switches to `技能` (the actual translation every other Chinese localized site uses for "skills" in this context). Bonus: catalog-row horizontal padding unified at 24px to match the fix already applied across `featured-card` / `template-card` / `system-card` / `source-card` (see PR #2600). The skill list grid on `/skills/` was the last family still on `padding: 22px 0`. Surface area - Marketing site only. `apps/landing-page/app/_components/header.tsx`, `app/i18n.ts`, `app/sub-pages.css`, `app/globals.css`. - No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces. - No new dependencies. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev: `/zh/` shows `资源库 / 教程 / 博客`; `/zh/` Library dropdown shows `技能 / 设计系统 / 模板 / 工艺` without count badges. - Local dev: `/` Product dropdown shows only Open Design + HTML Anything (no Tutorials child); top-row Tutorials link is the single canonical entry to that page. * fix(landing-page): keep catalog-row horizontal padding at narrow widths #2610's catalog-row 24 px fix only addressed the desktop rule. The `@media (max-width: 720px)` override block still set `.catalog-row a { padding: 18px 0 }` — zero horizontal — which is what Joey's screenshot was actually showing on narrow viewports. Switches the narrow padding to `18px 20px`. Matches the same narrow inset `.source-card` already uses (line 1058 of this file) so the catalog-row family stays in lockstep with the rest of the catalog tile families at narrow widths. Also drops the now-stale hover override (`padding-left: 8px; padding-right: 8px`) — that rule existed only to compensate for the zero-horizontal default and now produces a visible jolt against the new 20 px base. * fix(landing-page): featured-card description honors the 24px gutter Same shorthand fragility pattern that #2600 fixed for template-card, just one card family further down the file. `.featured-card p` (the description line on each featured strip tile, e.g. "Nº 0.001 deck-swiss-international" with the 16-列网格 blurb under it) had `margin: 0 0 12px` — three-arg shorthand, which expands to TOP RIGHT BOTTOM = LEFT, so margin-left and margin-right both got reset to 0. The group rule `.featured-card a > * + *` had set the horizontal margin to 24px so every text child sits inside a clean 24px gutter from the card border. The shorthand on `<p>` overrode the group rule and the description text bled right up against the card's right edge. Switches to `margin-bottom: 12px` only. Horizontal margin stays owned by the group rule, matching the fix template-summary already got in #2600. Surfaced by Joey on `/skills/` featured strip — the exact symptom he's been calling out for the last few rounds. Same shape, different file. --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
439f071cb0 |
feat(landing-page): replicate #2469 SEO content with deploy + regression fixes (#2605)
* chore(landing-page): bring PR #2469 content wholesale onto post-revert main Step 1 of replicating @pftom's #2469 work without the deploy-blocking issues that forced #2603. This commit copies the full \`apps/landing-page/\` diff from #2469's HEAD (`9d2a4f1`) onto current main verbatim — every i18n bundle, every page rewrite, every \`[locale]/\` wrapper. Subsequent commits on this branch then surgically restore the SEO fixes that #2469 silently regressed and configure the sitemap to survive the Cloudflare Pages 25 MiB limit, so deploy is healthy when this lands. What's in this commit - Tom's i18n bundle: \`i18n.ts\` (5377 lines), \`home-page-i18n.ts\`, \`info-page-i18n.ts\`, \`landing-ui-i18n.ts\`, \`content-i18n.ts\` (~10K lines total of locale data) - 18 landing-page locales: en, zh, zh-tw, ja, ko, de, fr, ru, es, pt-br, it, vi, pl, id, nl, ar, tr, uk - All existing pages rewritten to consume the new i18n bundle - Full \`[locale]/<route>/\` wrapper tree for every catalog page - \`plugin-registry.ts\` rewrite, \`catalog.ts\` adjustments - \`astro.config.ts\` route + sitemap reconfiguration - \`public/_headers\`, \`public/_redirects\`, \`public/favicon.svg\` adds - \`_components/locale-switcher-script.astro\` add What's intentionally NOT done in this commit (handled in follow-ups on this same branch): - Restore brand mark 44px + rounded corners (was lost from #2588) - Restore HA SoftwareApplication \`alternateName\` array (was lost from #2566) - Restore HA \`url\` canonical pointing at the landing page (was lost from #2586) - Restore Product/Library/Tutorials/Blog nav grouping (was lost from #2588) - Restore catalog-card padding 24px (was lost from #2600) - Configure sitemap to filter \`[locale]/\` routes so the generated XML stays under 25 MiB and Cloudflare Pages accepts the deploy - Add \`/zh-CN/* → /zh/*\` redirects for backwards-compatibility with any externally-linked OD-canonical locale URLs Validation so far - \`pnpm --filter @open-design/landing-page typecheck\` — 0 errors * fix(landing-page): unblock deploy + restore SEO regressions on top of #2469 Step 2 of replicating @pftom's #2469. The previous commit on this branch brings #2469's content wholesale; this commit applies the surgical fixes that make the result actually deploy and preserves the SEO improvements that #2469 silently regressed. Fix 1 — sitemap stays under Cloudflare Pages 25 MiB upload limit - `astro.config.ts` `filter` now drops every `/{locale}/...` route so the sitemap only emits canonical English URLs. - Locale variants are still discoverable via the `<xhtml:link rel="alternate" hreflang="...">` annotations the `namespaces.xhtml: true` option emits inside each canonical entry. This is Google's recommended pattern for a multi-language site. - Verified: post-fix `out/sitemap-0.xml` = 179 KB (was 38.4 MiB on the prior attempt that forced #2603's revert). Fix 2 — header brand block restored to the polished version - Logo `width/height` 36 → 44 (matches PR #2588's brand-mark refresh for visual weight against the new black speech-bubble glyph) - `.brand-meta` block ("Studio Nº 01 · Berlin / Open / Earth") removed from the header bar; the same editorial flourish still lives on the rotated `.side-rail .rail-text` pseudo-elements at page edges. Fix 3 — header nav grouped into Library + standalone Tutorials/Blog - Skills / Systems / Templates / Craft are now children of a Library dropdown (matches PR #2588's grouping). Each row keeps its count badge inline; the trigger highlights when any of the four facet pages is active. - Tutorials and Blog stay as standalone top-row items (PR #2588's original decision after Joey's review on the Learn dropdown). - Contact removed from the header — it was a same-page anchor that the footer already surfaces. - Hardcoded "Library" / "Tutorials" labels match the brand-name pattern: unlocalized across all 18 landing-page locales. Fix 4 — HA SoftwareApplication entity canonicalized on the LP again - `alternateName` is back to an explicit array of real query variants `["html anything", "html-anything", "htmlanything", "HTML Anything Editor", "The agentic HTML editor"]`. #2469 re-routed it through `copy.schemaAlternateName` which dropped the literal alias declarations Google needs for spaced-vs- hyphenated-vs-joined matching. (Restores PR #2566.) - `url` flips back from `HA_URL` (the GitHub repo) to the LP URL itself, matching the `BreadcrumbList` block on the same page. GitHub repo lives in `sameAs` as a peer surface. (Restores PR #2586. Without this, Google credits the GitHub repo as canonical for the entity, which is the opposite of what this surface exists for.) Fix 5 — catalog-card horizontal padding unified at 24 px - featured-card 22 → 24, template-card 20 → 24, system-card 18 → 24, source-card 28 → 24. - For template-card, also moved horizontal padding into the group rule exclusively so future siblings join without re-asserting margin shorthands. (Restores PR #2600.) Fix 6 — `_redirects` for the locale-code rename - This bundle uses `zh` / `zh-tw` / `pt-br` / `es` (the codes Tom's i18n.ts ships). The previous OD landing-page used `zh-CN` / `zh-TW` / `pt-BR` / `es-ES`. Externally-indexed and inbound-linked URLs against the old prefixes now 301 to the new canonical. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - `pnpm --filter @open-design/landing-page build` — completed successfully; 18,204 pages built; sitemap-0.xml is 179 KB (well under the 25 MiB Cloudflare Pages limit). * docs: promote 'open-source alternative to Claude Design' to README H1 Brings the missing README and .gitignore changes from #2469 that the first wholesale-checkout in this branch missed (the auto-pulled diff scope was filtered to apps/landing-page/ initially). What - Every README.*.md (13 locale variants) now leads with the "open-source alternative to Claude Design" tagline as a subtitle to the project name in the H1 / first paragraph. This was @pftom's brand-positioning commit (`ee851dc`) on the original #2469 branch. - `.gitignore` adds `growth/**` to keep growth-research scratch out of the repo. Why - The README is one of the highest-PageRank surfaces a GitHub project exposes to Google. Promoting the "alternative to Claude Design" framing into the H1/subtitle position makes the project surface for exactly the query the SEO work in this PR is trying to capture. - Without this commit, the replicated #2469 in this branch would still rank against the previous H1 ("Open Design") on GitHub crawls, letting the SEO win at the LP fall short on the GitHub surface. This is a strict subset of #2469's content — pure docs, no code, no behavior change beyond what GitHub renders on the repo overview. --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
eefaf4504a |
Revert "Enhance landing page with SEO-focused content and FAQ section (#2469)" (#2603)
This reverts commit
|
||
|
|
26ee030b4c |
Enhance landing page with SEO-focused content and FAQ section (#2469)
* Enhance landing page with SEO-focused content and FAQ section - Updated `.gitignore` to include growth directory. - Modified `astro.config.ts` to prioritize high-intent landing pages for SEO. - Added new FAQ styles and layout in `globals.css` for better user experience. - Implemented FAQ section in `page.tsx`, ensuring it aligns with structured data requirements. - Created dedicated pages for agents and alternatives to Claude Design, enhancing SEO and user navigation. - Introduced comparison page for evaluating Open Design against competitors. - Added favicon links component for consistent branding across all pages. * Add SVG favicon and update favicon links for improved branding * Enhance landing page with official source pillars for improved branding and navigation - Added five canonical "official source" pillars to the homepage, reinforcing key links: official site, GitHub repository, releases, documentation, and Discord community. - Updated URLs for releases, issues, documentation, and license to streamline access and improve user experience. * Add locale support and enhance landing page with language switcher - Introduced locale management with a new i18n module, defining multiple languages for the landing page. - Implemented a locale switcher in the topbar and header, allowing users to select their preferred language. - Updated global styles for the locale selector and adjusted layout for better responsiveness. - Enhanced SEO by ensuring localized content is served based on user selection. - Added a script for automatic locale detection and persistence in local storage. * Implement localized routing and enhance navigation with href utility - Added `stripLocaleFromPath` and `localizedHref` functions to manage locale-based URL paths. - Updated `localePath` to normalize paths based on the detected locale. - Refactored links in the header, footer, and main page components to utilize the new `localizedHref` function for improved navigation. - Introduced locale-aware routing for new pages, ensuring consistent user experience across different languages. - Enhanced SEO with alternate links for localized content in the sub-page layout. * Update header component to use new logo format - Replaced favicon image with a new logo in WebP format for improved performance and quality. - Ensured consistent branding across the landing page with the updated logo. * Enhance landing page with localization and new UI components - Introduced a comprehensive localization schema for content management, allowing for multilingual support across various sections. - Updated the blog and collection schemas to include internationalization (i18n) fields for better content localization. - Implemented a new official source strip for improved navigation and branding, linking to key resources like the official site and documentation. - Enhanced the locale switcher functionality, allowing users to select their preferred language with improved UX. - Updated styles for the locale switcher and added new components for better responsiveness and accessibility. - Refactored existing components to utilize localized URLs, ensuring a consistent user experience across different languages. * Implement comprehensive localization and enhance landing page UI - Introduced new localization features, including `EXTRA_LOCALIZED_HOME_BODY_COPY` and `EXTRA_LOCALIZED_LANDING_UI_COPY`, to support multilingual content across the landing page. - Updated `astro.config.ts` to integrate internationalization (i18n) settings for the sitemap, improving SEO for localized content. - Created new files for home page and info page internationalization, defining structured content for various languages. - Enhanced the locale switcher functionality with improved UX, allowing users to easily select their preferred language. - Refactored existing components to utilize localized content and URLs, ensuring a consistent experience across different languages. - Made CSS adjustments for better responsiveness and accessibility in the UI components. * Enhance landing page with new design templates and localization improvements - Added support for design templates in the content management system, allowing for better organization and access to design resources. - Implemented comprehensive localization for blog topics, enhancing multilingual support across various sections of the landing page. - Updated the header component to include new product menu items, improving navigation and user experience. - Refactored CSS for improved responsiveness and accessibility, including a new sticky chrome bar for better navigation. - Enhanced the locale switcher functionality, ensuring a seamless experience for users selecting their preferred language. * docs(readme): promote 'open-source alternative to Claude Design' tagline to subtitle across locales |
||
|
|
e5bea2c134 |
feat(landing-page): SEO surfaces, schema upgrades, manifest — cherry-pick from #2469 (#2596)
Pulls the high-value, low-conflict slices of @pftom's #2469 onto current main. That PR's branch was based on `af63af3` (May 20) and diverged from a fast-moving main, so its Tier 1 SEO content is shipped here as a fresh PR rather than rebased. What's included - **5 new SEO landing pages**, English-only for v1: - `/official/` — brand-authority hub naming every alias the project is searched as (Open Design, OpenDesign, open-design, opendesign, Open Design AI, OD); Organization JSON-LD with the same alternateName. - `/quickstart/` — three-command install path with HowTo JSON-LD, requirements, expected output, troubleshooting, next steps. - `/agents/` — 17 BYOK adapters across three tiers, ItemList JSON-LD, BYOK explainer. - `/compare/` — evaluation-stage comparison hub vs Claude Design, Figma Make, v0, Lovable/Bolt, Open CoDesign; mandatory honest-limits block as FAQPage JSON-LD. - `/alternatives/claude-design/` — primary commercial-intent page; TL;DR, why-people-search, BYOK explainer, feature-comparison table, who-should-pick-which, migration steps, FAQPage JSON-LD. - **`[locale]/` wrappers** for each of the 5 new pages, generated via `PREFIXED_LOCALES` from OD's existing `_lib/i18n.ts`. Each wrapper is a thin re-export of the canonical English page; per-locale translations are a follow-up. - **`seo-head.astro` schema upgrades** — `inLanguage` on Article / WebSite / Blog JSON-LD, `availableLanguage` on the WebSite block, `og:locale:alternate` for non-current locales, hreflang `x-default` link. - **`favicon-links.astro` extracted as a shared component** so every page emits the same icon set; new sizes (favicon-16x16, favicon-32x32, android-chrome-192x192, android-chrome-512x512) generated from the current brand mark; `site.webmanifest` published for PWA / Android install affordances. - **`llms.txt`** restructured to lead with brand-alias declaration and list every new official entry point. The Sister Projects section introduced in #2566 stays. What's intentionally NOT included - @pftom's parallel i18n bundle (`i18n.ts` / `home-page-i18n.ts` / `info-page-i18n.ts` / `landing-ui-i18n.ts` / `content-i18n.ts`, ~10K lines of locale data). It duplicates and conflicts with OD's existing `_lib/i18n.ts` + `_lib/home-copy.ts`. Adapting the new pages to OD's existing system instead keeps the codebase on a single source of truth; per-locale translations of the new pages can land as a separate PR. - Mass-rewrites of existing pages (homepage, HA, Skills/Systems catalogs, blog detail). #2469's branch base predates a number of in-flight PRs that touched those files heavily; cherry-picking the rewrites would re-litigate already-merged work. - The `[locale]/<existing-route>/` routing tree for Skills, Systems, Templates, etc. Without translations, mirroring 26 routes per locale produces duplicate-content signals; that lift belongs with a per-locale copy push. Surface area - Marketing site only. No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces. No new dependencies. No env vars. - New favicon assets are static PNGs generated from the canonical brand-mark source via `magick`; committed as files, no pipeline. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev server: every new English route returns 200; every `[locale]/<route>/` variant (e.g. `/zh-CN/agents/`) returns 200; `/favicon.ico` and `/site.webmanifest` resolve. - Local dev server: `seo-head.astro` emits the new `inLanguage`, `availableLanguage`, `og:locale:alternate`, and `hreflang=x-default` signals; `<FaviconLinks />` renders the consolidated icon set including the new manifest link. Followups - Translate the 5 new pages and replace the English-content wrappers under `[locale]/`. - Swap `sub-page-layout.astro` to use `<SeoHead />` so the new schema upgrades reach all sub-pages, not just the homepage and any future pages that opt into SeoHead directly. - Mass-update the homepage and HA page along the lines of #2469's rewrite, but rebased onto current main rather than off `af63af3`. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
052f8097de |
fix(daemon): inject @-mention skills into system prompt (#2552)
* fix(daemon): inject @-mention skills into system prompt Generated-By: looper 0.8.1 (runner=worker, agent=opencode) * fix(daemon): compose ad-hoc skill mode and aliases Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) * fix(daemon): lazily load and stage ad-hoc skills Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) * test(daemon): assert staged skill files before spawn Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) * fix(daemon): compose skill metadata across @ mentions Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) * test(daemon): cover ad-hoc critique skill policy Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) * fix(daemon): preserve plugin skill composition Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) * fix(daemon): resolve conflicting composed skill surfaces Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) * fix(daemon): preserve primary skill surface Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) * fix(daemon): share resolved critique surface Generated-By: looper 0.8.1 (runner=fixer, agent=opencode) |
||
|
|
682a9c9a9a |
feat(landing-page): group header nav into Product / Library / Learn (#2588)
* feat(landing-page): group header nav into Product / Library / Learn Reduces the top-level nav from 7 link slots (Product, Skills, Systems, Templates, Craft, Tutorials, Blog, Contact — plus the locale switcher and two CTAs) to 3 dropdown groups, eliminating the row-overflow that forced the brand sub-meta to truncate at narrow desktop widths and cramming the hamburger panel on tablet. Why - The four catalog pages — Skills, Systems, Templates, Craft — share the same shape: a list page that links into per-record details. They are facets of one library, not competing peer destinations. Letting each occupy its own top-row slot was a policy of last resort when there were only four; with Product newly added and Tutorials newly added since #2452 + #2266, the row no longer fits. - Tutorials and Blog are both editorial reading surfaces (videos vs articles), so they pair naturally under one Learn group. - Contact in the top nav was always misleading — it links to `#contact`, a same-page anchor on the homepage CTA section. The footer already lists it. Promoting it to top-row real estate while the row was overflowing made no sense. What - New `Library` dropdown holds Skills (132) / Systems (150) / Templates (111) / Craft (11). Each row inside the panel keeps its count badge inline beside the label, plus a one-line blurb so the panel reads as a directory rather than an undifferentiated list. The trigger highlights `is-active` whenever any of the four facet pages is the active route, so users still see "you are inside Library" feedback. - New `Learn` dropdown holds Tutorials and Blog. Same pattern: inline blurbs, parent-highlight when either child page is active. - `Contact` removed from the header. The footer surface still lists it; the top-row capacity it occupied is reclaimed. - `.dropdown-num` CSS class added for the inline count badge inside dropdown rows, distinct from the existing `.num` (which floats above top-row links as a superscript). Active-state semantics are preserved — every sub-page that already passes `active="skills"`, `active="systems"`, etc. keeps working without changes; the new Library trigger reads those same values to decide whether to highlight itself. Surface area - Marketing site only. No `apps/web`, no `apps/daemon`, no contracts, no CLI surfaces. No new dependencies. No i18n keys (existing `copy.navSkills` / `copy.navSystems` / `copy.navTemplates` / `copy.navCraft` / `copy.navBlog` keep working; "Library" and "Learn" are universal English labels that don't need translation tables for this iteration). Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev server at narrow breakpoints (≤1080px) — hamburger panel flat-expands the new Library / Learn dropdowns; existing handler closes the panel on link click. - Local dev server at desktop widths (≥1180px) — top row now reads Product · Library · Learn, matching the design intent. * feat(landing-page): polish brand mark — bigger glyph, single-line wordmark Three small refinements to the header brand block, requested after the new logo + grouped-nav landed: 1. The "Open Design" wordmark was wrapping to two lines once the new nav groups (Product · Library · Learn) sat alongside it on narrow desktop widths. The hamburger fallback already hides the entire nav at ≤1080px so the wordmark has plenty of horizontal room there — wrapping was a layout glitch, not a width constraint. Applies `white-space: nowrap` on `.brand` and adds an explicit `.brand-name` span so the rule is intent-bearing rather than relying on the parent inline-flex behaviour. 2. The brand mark felt undersized against the wordmark's optical weight, especially with the new black mark where the negative space inside the speech bubble visually shrinks the glyph. Bumps the displayed size from 36×36 to 44×44 (+22%) in both the header and the footer, so the proportion matches what users see in the logo.webp source. 3. Removes the `<span class="brand-meta">` block ("STUDIO Nº 01 · BERLIN / OPEN / EARTH"). It was decorative metadata that earned a third of the brand block's horizontal real estate without carrying any user-facing function. Drops the JSX, drops the styles, drops the now-stale `display:none` overrides at the 1180 and 880 breakpoints. The same rotated brand-string still lives on the side-rail pseudo-elements at the page edges (`.side-rail .rail-text`) — that surface is the canonical home for that editorial flourish, not the nav. Surface area - Marketing site only. `_components/header.tsx`, `page.tsx` (footer brand block), and `globals.css`. No `apps/web`, no `apps/daemon`, no contracts, no CLI. No new dependencies, no i18n changes. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev server: header HTML no longer contains `brand-meta`; the brand block on `/` and any sub-page renders the bigger 44px glyph + single-line "Open Design" wordmark. * refactor(landing-page): unbundle Learn dropdown and round the brand mark Two small follow-ups after looking at the rendered nav: 1. Reverts the Learn dropdown back to two standalone top-row links (Tutorials, Blog). Rolling exactly two items into a dropdown adds a click without reclaiming any horizontal space — the Library grouping is what genuinely cleared the row, and Tutorials + Blog can live side by side. Active-state semantics are preserved automatically since both pages already pass `active="tutorials"` and `active="blog"`. 2. Adds `border-radius: 10px` to the brand-mark image. The source PNG is a solid-fill square; clipping the corners gives the mark the modern app-icon silhouette (~22% of side length, matching the iOS / macOS convention) so it reads as a brand glyph rather than a raw screenshot next to the wordmark. Applies to both the header and footer brand blocks via the shared `.brand-mark img` selector. Validation - `pnpm --filter @open-design/landing-page typecheck` — 0 errors - Local dev server: top row reads `Product ▾ · Library ▾ · Tutorials · Blog` - Local dev server: brand mark renders with rounded corners on `/` and any sub-page (header + footer reuse the same `.brand-mark` styling) --------- Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
7631539808 |
Add project bulk delete feedback (#2481)
* Add project bulk delete feedback * Restart bulk delete toast timer --------- Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com> |
||
|
|
0f1b74ee44 |
fix(landing-page): canonicalize HA SoftwareApplication on the landing page (#2586)
Follow-up to #2566. That PR introduced `url: HA_URL` plus a two-element `sameAs` on the SoftwareApplication block, which declared the GitHub repo as the entity's canonical surface — Google uses `url` to decide which page to attribute structured-data entities to. The BreadcrumbList block right below uses the landing page URL, so the two blocks contradicted each other on what the canonical page was. Since the goal of #2566 was making "html anything" surface the landing page in search, canonicalizing on GitHub was an inversion of intent. This commit switches `url` to the landing page URL and keeps `HA_URL` only in `sameAs` so the GitHub repo is registered as a peer reference surface, not the canonical one. The two structured-data blocks now agree. Surfaced by Looper (PerishCode) on the #2566 review thread; the fix landed too late to ride the original PR, hence this small follow-up. Co-authored-by: Joey-nexu <joeylee12629@gmail.com> |
||
|
|
8269b7df90 |
Keep live artifact preview mounted across tabs (#2482)
Co-authored-by: Ghxst <200635707+GHX5T-SOL@users.noreply.github.com> |