mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
3a92f8a0111a27c6e91da28ac7249c04bfc0ef34
2484
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3a92f8a011 |
Add web effect extractor scenario (#4811)
Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local> |
||
|
|
488e3d6a6d |
[codex] Refine rich design-system chip previews (#4806)
* fix(web): refine design system chip preview * fix(e2e): extend home catalog visual capture budget Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(web): restore home ask mode toggle Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(e2e): dismiss consent before settings menu click Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(web): remove Hyundai preset brand --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
41f11f478f |
feat(analytics): split project_kind by Home task card (wireframe/mobile/document) (#4769)
* feat(analytics): split project_kind by Home task card The created-project analytics `project_kind` collapsed several Home task cards into coarse buckets: Wireframe / Mobile / Live artifact all reported `prototype`, and Document reported `other`, so a created project's kind no longer matched the `task_chip` the user picked. Extend `TrackingProjectKind` with `wireframe` / `mobile` / `document` (`live_artifact` / `hyperframes` already existed) and derive the finer kind from the persisted project metadata — `projectKindToTracking` now reads `fidelity` / `intent` / `platform`/`platformTargets` (precedence live_artifact > wireframe > mobile), and `other` + `intent: 'document'` becomes `document`. A new `projectKindFromMetadataToTracking` helper threads this at every call site (web project_create_result / recent-projects / designs / in-project runs, daemon run_created / run_finished). Product behavior is unchanged: `metadata.kind` stays `prototype`/`other` (cards keep reusing the web-prototype seed); only the analytics dimension splits out. `intent: 'document'` is an analytics-only tag no product logic keys off. HyperFrames already resolved correctly via `videoModel === 'hyperframes-html'` and is preserved. Verified end-to-end against a live daemon: created one project per card via the real API, read back the persisted metadata, and confirmed each derives its expected fine kind (hyperframes/wireframe/mobile/document/live_artifact). * fix(analytics): keep design-system-apply target kind coarse Review catch: ProjectView's in-project design_system_apply_result emits `target_project_kind`, typed `TrackingDesignSystemApplyTargetKind` — a narrower enum without the prototype subtypes (wireframe/mobile) or document. Switching that site to the widened metadata helper let a Home-created Wireframe/Mobile/Document project emit a value outside the field schema (the `as` cast hid it). Derive the coarse kind here instead, so subtypes collapse back to `prototype`; the fine split stays on `project_kind` only. --------- Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com> |
||
|
|
514ddba8ed |
ci: consolidate runner selection contract (#4803)
* ci: consolidate runner selection contract * test: stabilize connector settings privacy state |
||
|
|
a06ebdee09 |
fix(landing-page): make Product/Resources nav items pure dropdown toggles (#4784)
* fix(landing-page): make Product/Resources nav items pure dropdown toggles The Product and Resources top-level nav items were <a> links: Product navigated to the homepage and Resources to /blog/, which is confusing (they are category labels, not pages) and meant that on touch devices with no hover — touch laptops / tablets at desktop width — tapping them navigated away before their dropdown could open, leaving the dropdown children unreachable. Render both as <button data-nav-dropdown-toggle> disclosure controls that open their own dropdown instead of navigating. Hover still opens them on pointer devices (CSS); click/tap now toggles an .is-open class that reveals the dropdown, so touch users can reach the children. The four hub items (Solution/Agent/Plugins/Community) stay as links to their real hub pages. No crawlable links are lost: the homepage is still linked from the brand logo and the Product dropdown's 'Open Design' item, and /blog/ from the Resources dropdown's 'Blog' item. The homepage (index.astro) inlines its own copy of the header enhancer script rather than using header-enhancer.astro, so the toggle wiring is added in both places. * fix(landing-page): close click-pinned nav dropdown when another item activates Address review: clicking Product/Resources pinned that li in .is-open until an outside click, but the other header items still open on pure CSS :hover / :focus-within, so click Product -> hover Solution rendered both flyouts at once. Close any click-pinned dropdown on sibling mouseenter / focusin so a pinned panel and a hovered panel can never show together. Mirrored in the homepage's inline enhancer copy (index.astro) and the shared header-enhancer.astro. * fix(landing-page): drive Product/Resources dropdowns solely by .is-open Address review: clicking a toggle a second time or pressing Escape cleared .is-open and set aria-expanded=false, but the panel stayed visible because CSS still revealed it on :hover / :focus-within while the clicked trigger kept focus — visual and ARIA state diverged. Mark the two disclosure menus with .has-dropdown-click and exclude that class from the :hover / :focus-within reveal (caret, panel, and frosted backdrop), so an explicit close always wins. The four hub menus keep their hover/focus-within flyout behaviour. Product/Resources now open on click/tap/Enter only — correct disclosure-button semantics. Mirrored across header.tsx markup and the shared globals.css; the JS toggle in header-enhancer.astro and index.astro is unchanged. * fix(landing-page): treat Product/Resources as section headers on mobile Address review: at <=1080px the hamburger menu flattens every dropdown to always-visible, but the new disclosure buttons kept aria-expanded="false" and only flipped ARIA on click — so on phones the control announced 'collapsed' over already-visible links and tapping produced no visible change. Make the contract breakpoint-aware: on the flattened mobile menu the triggers drop their disclosure ARIA (aria-expanded / aria-haspopup) and the click is a no-op, i.e. they are plain section headers; on desktop they stay .is-open-driven disclosures. A matchMedia listener re-syncs the mode when the breakpoint is crossed. Mirrored in header-enhancer.astro and index.astro. * fix(landing-page): make Product/Resources real disclosures on mobile too Address review: the prior mobile path stripped aria-expanded/aria-haspopup and no-op'd the click in JS, but the SSR markup still shipped focusable <button> disclosures with stale ARIA over already-visible links — dead controls before/without JS. Make the mobile contract truthful end-to-end in CSS instead: at <=1080px the .has-dropdown-click panels are collapsed (display:none) until .is-open and their caret is shown as the expand affordance, so they are genuine accordions whose aria-expanded matches visibility even in SSR. The four hub menus stay flattened-visible. The JS is now a single .is-open toggle at every breakpoint (no breakpoint special-case). * fix(landing-page): reveal Product/Resources via pure CSS, no JS Address review: excluding the click-toggle menus from :hover / :focus-within left JS as their only open path, regressing the static-export/no-JS behavior the header relied on (dead buttons on first paint or script failure). Drop the JS .is-open disclosure layer entirely: Product/Resources are plain <button class="nav-trigger"> triggers (so they never navigate) whose dropdown is revealed by the exact same pure-CSS :hover / :focus-within rule as the four hub menus. This works with no JS, on first paint, and on touch (tapping the button focuses it, so :focus-within reveals the panel), and it removes the entire class of state-divergence bugs the JS layer introduced (pinned-vs-hover overlap, close-vs-focus-within, mobile aria). header.tsx markup + globals.css; the toggle JS in header-enhancer.astro and index.astro is removed. --------- Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com> |
||
|
|
2f75200a84 | chore(readme): add UTM attribution to open-design.ai links (#4792) | ||
|
|
d54f3e9759 |
feat(landing): upgrade 20-minute tutorial to long-form localized guide (#4693)
* feat(landing): upgrade 20-minute tutorial to long-form localized guide Turn the CodingMenace catalogue entry into a full original getting-started tutorial (install paths, first-run setup, workspace tour, first prototype, FAQ), rewritten in OD's voice and corrected against the current README (e.g. install is `pnpm tools-dev run web`), crediting and linking the original video. Figures are clean screenshots re-captured from the live site, not the creator's frames. Render: add long-form .tut-body rules (inline images + captions, code blocks, tables, hr) and a per-slug/locale long-form store (app/_data/tutorials-longform-i18n.ts). Non-English detail pages prefer this store and fall back to the existing generic placeholder, so the ~40 short catalogue entries are unchanged. i18n: localize to the priority languages from the SEO action plan — English source plus es (Tier A) and fr/de/ja/it/ru/pt-br/tr (Tier B). Lower-tier locales intentionally keep the placeholder rather than mass-generating machine translations on a low-authority site. * fix(landing-page): drop hardcoded catalog counts from 20-minute tutorial AGENTS.md forbids hardcoded visible "X skills / Y systems" claims because content edits can publish contradictory totals against getCatalogCounts(). Replace the fixed "129 design systems and 100+ skills" line with a count-free phrasing across the English source and all 8 localized bodies (de/fr/es/pt-br/it/ja/ru/tr). * feat(landing-page): add zh + ko long-form bodies for 20-minute tutorial Extends the pilot tutorial's localized coverage to the full keep-11 locale set (en + de/es/fr/it/ja/ko/pt-br/ru/tr/zh). zh and ko were the two canonical languages still missing a long-form body; both are quality-translated (brand names, commands, URLs, and file names kept verbatim; count-free library phrasing preserved). * feat(landing-page): use real in-app screenshots + address review nits on 20-min tutorial - Replace the three marketing-site captures with real Open Design app screenshots (workspace home, design-system picker, template picker) so the how-to figures match what users actually see in the product. - Drop the hardcoded http://localhost:7456 web-UI address from the English source and all localized bodies; tools-dev now resolves a dynamic port. - Narrow the caption CSS selector to p:has(> img) + p > em:only-child so it only styles image captions, not every em-only paragraph. --------- Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com> |
||
|
|
273bacfc78 |
[codex] Add design-system tracking analytics (#4783)
* feat(analytics): design-system tracking foundation (project_kind, entry source, brand picker)
Implements the first slices of the design-system tracking spec:
- §1 project_kind: brand-extraction backing projects now report
project_kind=design_system so DS-project runs (creation + edits)
drill down cleanly instead of collapsing into 'brand'.
- U3/U4: report design_system_source from the real selection source
(request->user_selected, plugin->template_inherited, project->
project_saved, app-default->default) instead of the hard-wired
unknown/not_applicable; add design_system_kind (official/custom,
derived from the user:<id> shape) and design_system_slug (official).
- §3.1 create entry source: thread the real entry (onboarding /
design_systems_page / composer_picker / home_card / library /
project_canvas) from each navigate() call site into the create
page_view and create_result, replacing the binary heuristic.
- C7-C9 preset-brand picker: instrument the "Start from a brand"
trigger click, picker surface_view, and brand_pick with
preset_brand_category (never the domain).
Contracts: add TrackingDesignSystemKind, TrackingDesignSystemEditSurface,
DesignSystemsPresetBrandPicker{Click,SurfaceView}Props; extend
RunCreatedProps with design_system_kind/slug/edit_surface and both
entry_from enums with composer_picker/project_canvas/library.
Adds docs/design-system-tracking-spec.md.
Verified: contracts build + 170 tests, web typecheck + full suite (3463),
daemon typecheck + run-analytics/brand-extraction tests (70).
* feat(analytics): track AI-optimize (design_system_enrich) click
C13 (tracking spec §3.3): instrument the "AI Optimize" banner CTA on a
programmatically-extracted DS-as-project so the AI-conversion rate
(clicked ÷ programmatic creates) is visible.
Contracts: add design_system_enrich area, DesignSystemEnrichClickProps,
DesignSystemEnrichResultProps, and the design_system_enrich_result event
name (result event reserved for the C14 follow-up).
Adds an implementation-status table to the spec. C14 (enrich_result) and
C15 (ProjectMetadata.enrichmentStatus) remain follow-ups since they need
correlation with the async enrichment run's completion.
Verified: contracts build, web typecheck, brand-enrichment + analytics
tests (89).
* feat(analytics): report all design-system create sources (ds_source_origins)
Comment ②: multi-source creates were flattened to a single `mixed`
design_system_source, hiding which sources combined. Add a comma-joined
`ds_source_origins` (e.g. `source_url,local_code`) on create_result that
lists every source actually used, keeping the singular field for
back-compat. Mirrors the target_platforms/connectors multi-value convention.
Verified: contracts build, web typecheck.
* feat(analytics): distinguish onboarding build-DS vs skip-to-home (C2)
C2 (tracking spec §3.1): the final onboarding step forks into "Build a
design system" and going straight Home, but both reported
completion_type=completed_without_design_system, so the skip rate was
invisible. Parameterize runOnboardingCompletion so the build fork reports
completed_with_design_system and the home forks report
completed_without_design_system. Update the EntryShell onboarding test,
which clicked "Build a design system" yet asserted the without value.
Verified: web typecheck, EntryShell onboarding tests (24).
* feat(analytics): instrument design-system edit lifecycle (E1-E3)
Tracking spec §3.4/§3.6:
- E3 (direct module edits, ui_click area=design_system_edit, all carrying
edit_surface=direct_module + artifact_kind=design_system + the DS id):
DesignSystemsTab general ops (edit-with-agent / refresh / download / reset),
DesignKitView module buttons via the parent handlers (logo/font/image
upload incl. paste, color edit, logo/image delete, design_md edit-save),
and the BrandPreviewCard three actions (use-in-chat / open-project / delete).
- E1 (agent-routed edits): a DS-project run editing an EXISTING design system
now carries edit_surface — comment/mark from their entry_from, otherwise
chat. First-generation runs get none.
Contracts: DesignSystemEditClickProps (+ union); reuses the
TrackingDesignSystemEditSurface added earlier and RunCreatedProps.edit_surface.
Not wired (enum reserved / no handler today): kit_import, kit_open (external
link in the stateless kit view), design_md_copy/upload.
Verified: contracts build; web + daemon typecheck; DS/Brand/Kit web tests
(89) + daemon run-analytics tests (44).
* feat(analytics): AI-optimize result + enrichment status (C14/C15)
Closes the programmatic-vs-ai_refined comparison gap (tracking spec §6):
- C14: the AI-optimize run is tagged (analyticsHints.dsEnrichment) from the
ProjectView banner CTA; when it settles, the daemon emits
design_system_enrich_result (result / design_system_id / project_id /
run_id / duration_ms / error_code).
- C15: on a successful enrichment run the daemon flags the backing project
metadata enrichmentStatus='ai_refined' + enrichmentCompletedAt, so the two
DS cohorts can be split for retention/usage analysis.
Contracts: ProjectMetadata.enrichmentStatus/enrichmentCompletedAt; reuses
the design_system_enrich_result event + DesignSystemEnrichResultProps added
earlier. ProjectChatSendMeta gains dsEnrichment.
Verified: contracts build; web + daemon typecheck; daemon run-analytics +
brand-extraction tests (70); web brand-enrichment tests.
* fix(contracts): declare dsEnrichment hint in ChatAnalyticsHints
The daemon run route reads analyticsHints.dsEnrichment and ProjectView's
AI-optimize path sends it, but the shared ChatAnalyticsHints DTO did not
declare the flag — leaving the web/daemon HTTP shape ahead of the contract
layer so typed ChatRequest callers could not discover or type-check the hint
that drives design_system_enrich_result and the ai_refined metadata stamp.
Declare dsEnrichment?: boolean with analytics-only semantics and add a
request-shape test that compile-fails if the field is dropped again.
Addresses review feedback on PR #4740.
* chore: ignore .playwright-mcp capture output
Local Playwright MCP runs write YAML page snapshots and PNG screenshots to
.playwright-mcp/. These are ad-hoc local visual-check output (including local
UI/session state), not a maintained test fixture, and per the root AGENTS.md
local runtime data must stay out of git. Add the ignore rule so the directory
is never accidentally staged again.
* fix: repair AI optimize enrichment tracking
Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)
* fix: track design kit module edit clicks
Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)
* fix: stabilize project P0 catalog loading
Stub unrelated catalog endpoints in the scoped project workspace and runtime Playwright flows so the P0 checks assert their target behavior instead of waiting on large registry responses.\n\nGenerated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)
* fix: guard design-system tracking repairs
Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)
---------
Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com>
Co-authored-by: Looper <looper@noreply.github.com>
|
||
|
|
4bfe4ba8ca |
feat(landing-page): add Ikigai One customer story at /story/ikigai-one (#4748)
* feat(landing-page): add Ikigai One customer story at /story/ikigai-one Open Design's first customer story. Bespoke full-bleed landing page reusing the shared shell (header/footer/SEO), with per-locale variants so hreflang alternates resolve. * feat(landing-page): add /story index + Stories nav + 18-locale i18n for the Ikigai One story - New /story stories index (masthead, story card, value props) + Resources -> Stories nav entry, both localized. - Full 18-locale i18n for the story and index, via a generated ikigai-one-i18n module. - Content fix: feat01 is the 40-page branded report (not the brand book); ~25x -> ~50x; uses the report-cover image. - Drop the 'Contact sales' CTA for now — the enterprise lead page isn't live yet (per standup); Download-only. * fix(landing-page): rename /story -> /stories (plural) + SEO pass per review - Rename the route /story -> /stories across index, story, locale variants, nav entry, and assets (review: Joey). - SEO: shorten the story <title> to the quote + 'Ikigai One' (was ~114 chars); add descriptive alt text to the story images; add CollectionPage JSON-LD to the /stories index. * fix(landing-page): enrich /stories index title + meta description for SEO Index <title> now eyebrow + heading (~41 chars, en); meta description combines the sub + value-prop line (~155 after clamp). CJK locales are naturally dense. Resolves the short title/description audit warnings. * fix(landing-page): brand-PDF cost is several thousand dollars (per customer); aria-hidden on decorative SVGs - Problem section: the client-ready brand PDF costs several thousand dollars to produce, not ~$1,000 (Anthony's correction), in all 18 locales. - Add aria-hidden="true" to the decorative hand-drawn SVGs (brush strokes, circles, scribble, stat line, arrow) so screen readers skip them. --------- Co-authored-by: ScarletttMoon <scarletttmooon@gmail.com> |
||
|
|
9f4fe9ff8a |
fix(landing-page): stop :loc fold from shadowing /plugins hub pages (#4786)
The kept-locale catalog folds use a `:loc` placeholder
(`/:loc/{skills,systems,templates}/* -> /:loc/plugins/...`), but `:loc`
also matches the literal segment `plugins`. So the canonical English hub
pages `/plugins/{skills,systems,templates}/` bound `:loc=plugins` and
301'd to `/plugins/plugins/.../` (404), taking three live catalog pages
down. `/plugins/craft/` survived only because it has no fold below.
Guard the three hub paths with a 200 rewrite placed before the folds, so
first-match-wins serves the real static pages (and their faceted
`/plugins/templates/<kind>/` children) instead of redirecting.
Verified with `wrangler pages dev`: the three paths return 200 again
while every locale fold (`/zh/templates/*`, retired `/vi/*`, ...) is
unchanged.
Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com>
|
||
|
|
dd02f9bc7c |
Update official social links in settings menu (#4779)
Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local> |
||
|
|
3b602f51f4 |
[codex] Polish design-system preview UI (#4776)
* fix(web): polish design system preview release UI * fix(web): restore home carousel submit Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(web): keep design system spec sidebar Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(web): block carousel submit while plugins load Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(web): preserve design system preview modal behavior Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
5333098611 |
[codex] reduce noisy visual report matches (#4772)
* fix(e2e): reduce noisy visual report matches * fix(e2e): use normalized visual diff area Generated-By: looper 0.9.11 (runner=fixer, agent=codex) |
||
|
|
0c7955de69 |
fix(web): simplify run error source diagnostics (#4701)
* fix(web): simplify run error source diagnostics * fix(web): preserve fallback error source text |
||
|
|
db78a90b05 |
fix(landing-page): drop redirecting legacy routes from sitemap (#4773)
* fix(landing-page): drop redirecting legacy routes from sitemap The sitemap listed ~468 URLs that 301-redirect: the legacy /skills, /systems, and /templates catalog routes (now served under /plugins/* via public/_redirects) and legacy region-cased locale codes (zh-CN, zh-TW, es-ES, pt-BR, which 301 to /zh/, /es/). Their old page routes still build, so @astrojs/sitemap emitted them even though every one returns a redirect. A sitemap should carry only final 200 canonical URLs; redirecting entries waste crawl budget and feed Google Search Console's 'Page with redirect' exclusion bucket. Filter the legacy route and locale prefixes out, and drop the now-dead /skills//systems//templates/ branches from the priority serializer. Sitemap drops 1,217 -> 749 URLs (the 468 redirects), with no 200 page removed (verified against a full crawl of the live sitemap). * fix(landing-page): keep canonical section hubs on elevated crawl hint Address review: the legacy /skills, /systems, /templates roots 301 to /plugins/skills, /plugins/systems, /plugins/templates. Give those canonical hubs the same 0.7/weekly sitemap priority as the /plugins/ hub instead of the generic 0.5/monthly default, so dropping the redirecting roots does not quietly downgrade the crawl signal of their replacements. --------- Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com> |
||
|
|
8b9b169673 |
ci: add cost-sensitive runner tiering (#4764)
* test persistent contabo runner poc * test persistent runner web workspace poc * test persistent runner local cache setup * test persistent runner aggressive setup poc * test persistent runner web postinstall profile * test restore stable persistent runner poc * test keep persistent runner web tests default * test route persistent poc to zxiyun runner * test relax web vitest timeout on persistent poc * test harden web vitest cases for persistent runner * test add runner perf probe * test isolate runner perf probe dispatch * test pin runner perf probe node * test expand runner perf probe matrix * ci: route heavy jobs through runner modes * ci: keep web workspace tests on blacksmith * test: update web runner mode assertion * ci: route cost-sensitive runner tiers * ci: move preflight to hosted runner * ci: downshift blacksmith ui runners to 4vcpu * test: fix file viewer timeout after merge * ci: route e2e vitest to hosted default * ci: centralize runner profile selection * test: type runner profile assertions * ci: route e2e vitest to default blacksmith * ci: remove runner experiment probes * test: restore postinstall guard coverage Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * test: cover postinstall script contract * test: move postinstall guard to e2e --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
4c6f6549c6 | ci: add landing QA Feishu summary (#4763) | ||
|
|
a5e2f1bcff | test(e2e): add target-level visual baselines (#4765) | ||
|
|
a4dea37a22 | fix(ci): harden backport automerge provenance (#4761) | ||
|
|
60d187b79f |
feat(ci): auto-merge the rolling plugin-preview manifest PR on green CI (#4727)
* feat(ci): auto-merge the rolling plugin-preview manifest PR on green CI The post-merge bake opens ONE rolling PR (chore/plugin-previews) whenever a baked preview changes (spec slice 1). Its diff is pure generated data (data/plugin-previews/manifest.json; the clips already live on R2), yet someone still had to merge it by hand — the last manual step the pipeline rework left behind, and exactly the kind of bot PR that used to pile up. Two coupled changes: 1. bake-plugin-previews.yml now authors the rolling branch + PR with the release-bot App token instead of GITHUB_TOKEN. A GITHUB_TOKEN-authored push does not trigger workflows, so the manifest PR never ran `Validate workspace` (main's required check) and could not clear the merge queue at all — even manually. (PREVIEW_BAKE_TOKEN, the old fallback, was never configured.) 2. bake-plugin-previews-automerge.yml reacts to ci.yml completing for the rolling branch and, for a non-draft same-repo PR authored by the release bot with only bot commits (pristine) at the exact SHA CI passed, approves it as github-actions[bot] (main requires one review; an author cannot approve its own PR) and enqueues it with `gh pr merge --auto --squash --match-head-commit`. CI failure pings Feishu instead. Modeled on backport-automerge.yml, adapted for main: main has a merge queue, so the merge is GitHub-native --auto (enqueue) rather than a direct squash, and pristine checks the git committer email (the bot email maps to no GitHub user, so .committer.login is null). * fix(review): GitHub-backed pristine provenance + correct Feishu sign secret Address two blocking review findings on the auto-merge path: 1. pristine could be satisfied by spoofed commit metadata. The bake commit was authored with a plaintext `git config user.email "bot@open-design.ai"`, and the reactor trusted that raw committer email — any collaborator who can push to chore/plugin-previews could set the same email and fake pristine=true, landing unreviewed commits on main. Commit AS github-actions[bot] (its canonical noreply email) and gate on GitHub's resolved `.committer.login`, the same GitHub-backed field backport-automerge.yml trusts. 2. the failure Feishu ping signed with secrets.FEISHU_WEBHOOK_SECRET, but every other FEISHU_RELEASE_WEBHOOK caller (notify-release-feishu, notify-daily-feishu, backport-automerge) signs with FEISHU_RELEASE_SIGN_SECRET; the wrong secret resolves empty and the signed release webhook rejects the unsigned request. Use FEISHU_RELEASE_SIGN_SECRET and assert the pairing in the topology test. * test(ci): lock the auto-merge conclusion gates + bake producer invariants Address nettee's non-blocking coverage follow-up so a regression can't pass the topology test green: - Assert the approve and enqueue steps gate on `workflow_run.conclusion == 'success'`, and the Feishu step on `== 'failure'`, so dropping a conclusion predicate fails the test. - Add a companion `[P2]` block over bake-plugin-previews.yml asserting the three producer invariants the downstream pristine/auto-merge path depends on: the create-github-app-token mint, the App-token push URL, and the github-actions[bot] commit identity. Regressing the producer back to a GITHUB_TOKEN push or a different commit identity now fails here. * fix(review): bind the Feishu failure alert to the run's exact SHA A stale failed CI run can finish after the force-pushed rolling branch has advanced to a newer (green) head. The Feishu step only gated on author/cross + conclusion==failure, so it would page about a SHA that is no longer the PR head. Add the same steps.pr.outputs.head_oid == workflow_run.head_sha guard the approve/enqueue steps use, and lock it in the topology test. * fix(review): prove bot provenance with GitHub signatures, not git headers nettee correctly escalated: .committer.login is still a spoofable git-header identity. A collaborator who can push to chore/plugin-previews can mint a local commit with the bot's noreply email, GitHub then reports .committer.login == github-actions[bot], and pristine=true passes — landing unreviewed human changes on main. Switch to non-spoofable provenance: - bake-plugin-previews.yml now creates the manifest commit through the Git Data API (blob -> tree -> commit -> ref) with the App token, instead of a local `git commit` + push. A commit created through the API is signed by GitHub (verification.verified=true) and committed by the App bot (committer.type=Bot). Building the tree on the live main HEAD also rebases the rolling branch onto the latest main each run. - the reactor's pristine gate now requires EVERY commit to be both verification.verified==true AND committer.type==Bot. A human's locally pushed commit is unsigned (verified=false) even with the bot email; a human signing with their own key is committer.type=User. Neither can be both, so the pair is forge-proof. - topology test updated to lock the API-commit producer path (and forbid a regression to a local push) and the verified+Bot pristine check. * fix(review): parent the manifest commit on the rendered revision, not live main The previous change based the Git Data API commit on live main HEAD, but $NEW is rendered from the job's checkout at start. The bake can run ~90min and main can advance (unrelated merges) meanwhile, so basing the commit on live main would publish a manifest from an older checkout on top of newer commits — the rolling PR would look current while carrying stale preview data and could overwrite newer plugin-preview changes. Parent on `git rev-parse HEAD` (the exact revision that produced $NEW) instead, so GitHub correctly shows the PR as behind once main moves and the next bake re-renders on the newer HEAD. Topology test locks the rendered-revision base and forbids regressing to a live-main base. * fix(review): require the rolling PR's rendered base to equal main before merge main's merge queue does not require an up-to-date branch, so a bake rendered against an older main could be re-tested on the latest main and squash-merged with a stale manifest.json — overwriting a newer manifest that landed meanwhile. head_oid only proves the PR head is the SHA CI validated, not that it is current. Add a base-freshness gate: the rolling commit's first parent is the revision the manifest was rendered against, so require it to equal current main HEAD before approving/enqueuing. A behind PR is skipped and refreshed by the next bake (the nightly re-renders on the newer tip even if no plugin changed). Locked in the topology test. * fix(review): page Feishu on timed_out bakes too, not just failure workflow_run has terminal RED states beyond 'failure'. ci.yml defines many timeout-minutes jobs, so a timed-out run on the rolling manifest PR would block auto-merge with no Feishu page. Broaden the alert predicate to failure OR timed_out, and include the conclusion in the message. Deliberately exclude 'cancelled': a force-push of the rolling branch routinely cancels the prior in-flight run, so alerting on it would page on every normal refresh. Test locks the failure+timed_out set and the cancelled exclusion. * fix(review): gate auto-merge on a manifest-only diff, not commit signatures nettee is right that the Git Data API does not sign commits — I verified it empirically: a blob/tree/commit created via POST /git/commits comes back verification.verified=false / reason=unsigned (and the Contents API is unsigned too for non-web callers). So the previous verified==true && committer.type==Bot pristine gate would never pass and the rolling PR could never auto-merge. There is no per-commit field that is both bot-set and unforgeable without a signature, so stop trying to prove "the bot authored every commit" and instead bound what auto-merge can land: require the PR's changed files to be EXACTLY data/plugin-previews/manifest.json. A human with push access can still push to the rolling branch, but any commit that touches a code file (or anything but the manifest) makes the file set != the single manifest path, so pristine=false and the PR falls back to human review. The worst a forged manifest-only change can do is point a gallery preview at a bad video URL — generated data the next bake overwrites — never unreviewed code on main. With signing no longer relied upon, revert the producer from the Git Data API dance back to a plain App-token git push (still parented on the checked-out HEAD per the base-freshness fix, still pushed with the App token so CI runs). Tests updated to lock the manifest-only file gate and the simplified producer. * test(ci): update secretless-bake assertion for the App-token producer Merge main (#4691 landed the read-only pre-merge bake + its topology test). That test asserted the post-merge workflow contains PREVIEW_BAKE_TOKEN as the write-capable credential; this PR replaced that never-configured fallback with the release-bot App, so assert secrets.RELEASE_BOT_APP_ID (and that PREVIEW_BAKE_TOKEN is gone) instead. |
||
|
|
2fb0ac5f26 |
feat(landing-page): concentrate to 11 SEO locales + 'best' over 'official' in homepage TDK (#4709)
* feat(landing-page): retire 7 low-traffic marketing locales GSC (last 90d) shows the marketing site's non-English traffic is highly concentrated: English ~80% (mostly homepage/brand), Spanish the clear #2, and a long tail of locales returning near-zero clicks. Retiring the tail removes thin/duplicate pages that dilute crawl budget on a low-authority domain, and concentrates SEO on the languages that actually earn. Keep 11 active locales (en, zh, ja, ko, de, fr, ru, es, pt-br, it, tr); retire zh-tw, vi, pl, id, nl, ar, uk. Implementation keeps the full historical list as ALL_LANDING_LOCALES so existing per-locale translation data stays type-valid, and derives the shipped LANDING_LOCALES by filtering out the retired set. Every consumer (routing, language switcher, hreflang, sitemap) imports LANDING_LOCALES, so new pages automatically ship only the active set and the retired languages cannot silently reappear. Add public/_redirects 301s for the retired locales (plus legacy zh-cn/fa/ hu/th prefixes still in Google's index): Chinese variants fold into /zh, everything else folds into the English page. * feat(landing-page): retire locales in catalog i18n system too The landing page has a second, parallel locale list in app/_lib/i18n.ts (PREFIXED_LOCALES) that drives the catch-all catalog/craft routes, separate from app/i18n.ts:LANDING_LOCALES. Filtering only the latter left a couple of catalog pages per retired locale. Exclude the retired locales from PREFIXED_LOCALES as well so no retired-locale pages are generated by either system. zh-CN/fa/hu/th also remain covered by the existing LEGACY_LOCALE_PREFIXES redirect handling. * feat(landing-page): use "best" over "official" in homepage SEO title/description The homepage TDK called Open Design the "official" open-source Claude Design alternative. "official" carries no search demand (nobody queries "official X") and reads as self-promotion. "best" matches the high-intent "best open-source X" / "best Claude Design alternative" pattern users actually search, lifting title CTR without changing the page's meaning. Scope is the homepage SEO copy only — HOME_SEO_COPY title + description across all 18 locales, plus the og.astro social-card title that mirrors it. Deliberately left untouched: "official vs community registries" on the plugins page, the /official/ canonical-disambiguation page, the HTML Anything canonical title, and the FAQ entity-disambiguation copy, where "official" is a meaningful claim rather than a CTR modifier. * fix(landing-page): keep retired-locale redirects under Cloudflare's dynamic-rule cap Cloudflare Pages `_redirects` ignores every rule past the 100th dynamic (splat/placeholder) entry and only logs a build warning. The file had already accumulated 113 dynamic rules, so the retired-locale catch-alls appended at the end never took effect — `/vi/`, `/id/`, `/zh-tw/` etc. returned 404 instead of the intended 301 to English, the exact soft-404 the prune set out to avoid. The per-retired-locale sub-path rules (`/<loc>/skills/*`, `/<loc>/plugins/*`, `/<loc>/plugins/previews/*`, …) are now dead weight: a single `/<loc>/* -> /:splat 301` folds the whole locale to its English (or `/zh` for Chinese variants) equivalent. Removing those 38 redundant rules drops the dynamic count to 75, so the catch-alls land inside the cap and fire. * refactor(landing-page): consolidate _redirects with :loc placeholders under CF cap The previous commit trimmed 38 dead per-locale rules but the file still exceeded Cloudflare Pages' total-rule cap (~131 rules on this project), which silently drops every rule past the cap. Retired-locale catch-alls and the later kept-locale plugin redirects landed in that dead zone and 404'd instead of 301'ing — verified on the PR preview and on production (`/de/plugins/*` already 404s live, pre-dating this branch). Collapse the per-locale catalog redirects (skills/systems/templates, plugins, plugins/previews — 53 rules across 11 locales) into five `:loc` placeholder rules, and drop the retired locales' now-redundant /wallet rules (their catch-all folds /wallet through to /amr/wallet). Total rules drop 169 -> 110, comfortably under the cap, so every redirect — retired folds to English/zh, and the kept-locale ones that were already dead on prod — fires again. Retired catch-alls are ordered before the generic /:loc/* rules so a retired locale folds to English, not to its own non-existent /plugins/ pages; real localized hub pages are static assets and win over these rules. * fix(landing-page): don't fold localized plugin hubs to English The consolidated `/:loc/plugins/* -> /plugins/:splat` rule shadowed the real localized plugin hub pages. On this project `_redirects` takes precedence over static assets (verified: `/es/plugins/` has a 231 KB static index but the rule 301'd it to `/plugins/`), so the rule regressed `/<loc>/plugins/`, `/<loc>/plugins/skills|systems|templates/` from 200 to a redirect. They only kept serving on production because the per-locale rule sat past the redirect cap and never fired. Drop the `/:loc/plugins/*` and `/:loc/plugins/previews/*` folds. Localized plugin hubs are static and now serve 200 again; localized plugin detail pages 404 exactly as they do on production today (no regression); retired-locale plugin paths still fold to English via the retired `/<retired>/* -> /:splat` catch-alls. Keep only the skills/systems/ templates legacy folds, whose source paths are not generated as static. * chore: re-trigger CI on updated main — needs-validation gate moved to merge_group (#4714) * fix(landing-page): retire zh-CN/es-ES/pt-BR catalog prefixes (full 11-locale convergence) The catch-all catalog routes and blog RSS feeds build from PREFIXED_LOCALES, which still included the BCP-47 aliases zh-CN, es-ES and pt-BR. Those are duplicates of the kept short codes zh, es and pt-br: the canonical pages are generated from LANDING_LOCALES, so the alias-prefixed pages only ever 301 back to the short code via public/_redirects, yet still count toward the Cloudflare Pages file-count cap. Add the three aliases to RETIRED_PREFIX_LOCALES so the catch-all no longer emits them. Every generated route now lands on the same 11 canonical short codes (en zh ja ko de fr ru es pt-br it tr); incoming alias URLs remain covered by the 301 migration rules. --------- Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
29b138f7a3 |
feat(brands): turn any brand into a reusable design system (#4691)
* Implement brand management routes and CLI support - Added `brand-routes.ts` to handle HTTP endpoints for brand operations: listing, extracting, retrieving details, deleting, and serving logos. - Introduced `brands-cli-help.ts` for CLI commands related to brand management, including usage instructions for listing, creating, retrieving, and deleting brands. - Updated `cli.ts` to integrate brand commands into the existing CLI structure, allowing headless management of brands via the command line. - Created supporting files for brand metadata handling, including `design-md.ts` for rendering brand information in markdown format and `index.ts` for the brand engine API. - Implemented `prefetch.ts` to fetch and process brand material from specified URLs, ensuring a streamlined extraction process. - Enhanced server setup in `server.ts` to register brand routes and manage brand-related data effectively. This commit establishes a comprehensive framework for managing brands within the application, facilitating both HTTP and CLI interactions. * Enhance memory management and onboarding experience - Introduced canonical profile labels to ensure consistent handling of user input in profile forms, preventing duplicate entries. - Updated the `parseProfileBody` and `captureProfileFromForm` functions to utilize the new canonical label matching. - Added a memory callout section in the onboarding view to highlight the benefits of memory usage, including personalized responses and reduced setup questions. - Implemented new UI elements in the onboarding view to improve user engagement with memory features. - Expanded i18n support for new onboarding messages related to memory benefits across multiple languages. * Refactor onboarding flow and enhance design system integration - Updated the onboarding process to include a new brand extraction step, replacing the previous newsletter step. - Adjusted the tracking logic to reflect the new onboarding steps, ensuring accurate analytics for user progress. - Improved the UI for the onboarding view, including new input fields for email collection during the brand extraction phase. - Refined the EntryShell component to remove outdated comments and clarify the onboarding renderer's purpose. - Enhanced CSS styles for the onboarding steps to improve layout and user experience. - Updated internationalization strings across multiple languages to reflect changes in the onboarding flow and brand extraction messaging. * Add brand management features and enhance font handling - Introduced new modules for managing brand assets, including `chrome.ts` for headless Chrome operations and `fonts.ts` for self-hosting web fonts. - Implemented `prefetch.ts` to streamline the brand material extraction process, allowing for efficient harvesting of colors, fonts, and logos. - Enhanced the brand system with new schema definitions in `schema.ts` to support brand color and font management. - Developed the `engine` module to integrate brand building and rendering processes, including token derivation and artifact generation. - Improved the overall structure and organization of brand-related files for better maintainability and scalability. * Enhance brand extraction and project management features - Updated `brand-routes.ts` to include new dependencies for project management, allowing for the registration of brand-related projects. - Modified the `extractBrand` function to support project ID and system files, improving the brand extraction process. - Enhanced the CLI commands in `cli.ts` to handle project IDs during brand creation, enabling better tracking of brand projects. - Updated the server setup in `server.ts` to register new project-related routes. - Improved the UI components to display project information associated with brands, including buttons for opening projects in the `BrandDetailView` and `BrandsTab`. - Added new metadata fields in the contracts to support project tracking and management for brands. This commit establishes a more robust framework for managing brand projects, enhancing both backend and frontend functionalities. * Enhance onboarding profile management and memory persistence - Added new canonical profile labels for 'Organization size', 'Use cases', and 'Discovery source' to improve user input consistency. - Introduced `OnboardingProfileState` type to manage onboarding profile data more effectively. - Implemented functions to build and persist the onboarding profile body to memory, ensuring user selections are saved accurately. - Updated the `OnboardingView` component to handle profile persistence during navigation and submission steps. - Enhanced tests to verify that user selections are correctly persisted to the memory profile. This commit improves the onboarding experience by ensuring that user inputs are consistently captured and stored, enhancing overall user engagement with the application. * Reflow brand extraction into an agent-driven, live flow Replace the deterministic SSE prefetch/preview/system pipeline with an agent-driven extraction: POST /api/brands now reserves the brand and stands up a backing project with the target site open in an in-app browser tab plus a seeded prompt, so the agent measures, synthesizes brand.json incrementally, and the user can clear anti-bot walls by hand. New /preview and /finalize routes let the agent render the kit page live and register the resulting user:<id> design system, so extracted brand facts persist as a structured, reusable brand kit instead of a one-shot deterministic guess. Adds the brand-extract skill (SKILL.md + brand-kit.html template), kit-render engine, brand-extraction-engine tests, brand project covers in the Designs tab, onboarding extract handoff, and the matching od brand extract/preview/ finalize CLI subcommands and contract updates. Co-authored-by: Cursor <cursoragent@cursor.com> * Sediment finalized brands into structured memory Reflow a finalized brand into the memory store (brandToMemoryEntries + reflowBrandToMemory) so future chats can ground vague requests in the brand's palette, type, voice and rules. finalizeBrand now wires through the runtime dataDir and best-effort persists the brand, MemoryChangeEvent gains a 'brand' source, and the brand kit render hardens its inline JSON escaping. Adds brand.previewEmpty / brand.viewDetails across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Implement logo fallback and imagery support in brand extraction - Introduced a deterministic logo fallback mechanism to ensure that brand extraction processes can retrieve and save site logos, even when the agent fails to do so. - Enhanced the `startBrandExtraction` and `finalizeBrand` functions to utilize the new logo fallback, allowing for better handling of logo assets. - Added support for imagery samples in brand validation, enabling the inclusion of representative images in the brand kit. - Updated the brand kit rendering to include self-hosted fonts and imagery, improving the overall presentation of brand assets. This commit strengthens the brand extraction workflow by ensuring that logos and imagery are reliably captured and displayed, enhancing the user experience in brand management. * Enhance memory management with rule proposal and verification features - Introduced new functionality for distilling annotations into rule proposals, allowing users to suggest rules based on in-canvas annotations through the `od memory rule suggest` command. - Implemented a verification system that programmatically enforces compliance with active rules during artifact generation, ensuring that all active rules are covered in the self-verify scorecard. - Added endpoints for managing verification outcomes, including listing, removing, and clearing verification records, enhancing the transparency of the verification process. - Updated the memory management system to support the retrieval of active rule entries, ensuring that only linked rules are considered during verification. - Enhanced tests for both rule proposal generation and verification processes to ensure reliability and correctness. This commit strengthens the memory management capabilities by integrating rule proposals and verification, improving the overall user experience in managing design rules and ensuring compliance. * Distill review annotations into memory and enforce self-verify scorecard Add distillAnnotationsToMemory to mine inline preview comments/highlights/ marks into durable feedback + rule memory via a dedicated distiller prompt, threaded through the existing extract pipeline with an 'annotation' change source. Tighten the self-verify prompt (daemon + contracts) to state the daemon programmatically checks the scorecard, so a missing or uncovered scorecard on an artifact turn is an enforcement failure. Cover the rule suggest and verification-history routes with tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Apply brand design system through web config on "Use in new chat" Thread onApplyDesignSystem from the entry shell into BrandsTab so the brand's registered design system is applied via the web config channel instead of a bare daemon PATCH that left the Home composer stale. Add a transient home-intent latch + event so the Brands tab can request the Prototype chip on the already-mounted HomeView, which consumes it once the plugin catalog loads. Co-authored-by: Cursor <cursoragent@cursor.com> * Wire annotation distillation into background memory extraction Add a background distill pass that mines inline review annotations (comments / highlights / drawn marks) from a turn into durable memory alongside the general LLM extraction, surface an `annotation` memory toast source in the web UI, and cover the flow with a unit test. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix brand design system not applying to composer on "Use in new chat" Selecting a brand's "Use in new chat" applies the brand's design system as the default and fires the Prototype chip intent in the same synchronous click handler. HomeView consumed that intent inside the event listener, so `pickChip` ran before React committed the config change and seeded the composer's design-system field from the stale (empty) default — the composer showed "No design system" instead of the brand until a reload. Split the intent handling: the listener now only bumps a tick, and a separate effect consumes the chip after the re-render lands, so the seeded design system reflects the freshly-applied brand. Add the previously-untracked home-intent latch test coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): rework Brands into Brand Kit and add Home create entry Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): harvest real cover/hero images for the Images module The brand kit's Images gallery only populated when the extraction agent remembered to save imagery — so a forgetful or bot-blocked agent (and the pre-imagery "Open Design" brand) left it empty. Add a deterministic, server-side imagery fallback (imagery-fallback.ts), mirroring the logo fallback: it parses og:image/twitter:image, large <img> (highest-res srcset/<picture>), <link rel=preload as=image>, and CSS background-image hero blocks, fetches candidates with browser-shaped headers, decodes PNG/GIF/JPEG/WebP dimensions to keep only big representative images (dropping icons/sprites/logos/tracking pixels), dedupes by content hash, and saves up to 8 of the largest into imagery/ with labeled samples. finalizeBrand runs it as a timeout-bounded, failure-tolerant safety net (injectable so tests stay offline) when the agent captured too few samples, first adopting any on-disk images. The extraction prompt and brand-extract SKILL now explicitly direct the agent to harvest the site's large/cover/hero images, filtered by rendered size. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(qa): implement deck layout validation and safety checks Add a new QA module for validating the layout of generated brand decks to ensure robustness against clipping and truncation issues. The `analyseDeckLayout` function checks for critical layout invariants, including the presence of `.slide` sections, correct container types, and necessary runtime layers. Introduce `assertDeckLayoutSafe` to enforce these checks during brand system rebuilds, preventing the deployment of decks that fail validation. Additionally, create comprehensive tests to verify the functionality of the new layout validation features. * fix(brands): apply deck shrink-to-fit synchronously so slides never clip The no-clip runtime scheduled its fit pass through requestAnimationFrame, whose callbacks are throttled while the deck is offscreen or occluded. A slide could therefore stay unscaled — and clip its content — until first paint. Fit synchronously on resize/load/fonts-ready with a trailing setTimeout settle pass for late reflow, removing the rAF dependency. Verified at the previously-broken 1024x620 viewport: container-type:size, zero truncations, runtime auto-applies scale (Problem 0.71, ASK 0.87, Product 0.97, Competition 0.97) and frame clip count drops to 0. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): let New Brand modal embed scrollable brand reference picker Add a fillHeight mode to BrandReferencePicker so the heading, quick-pick row and controls stay pinned while only the gallery scrolls inside a bounded-height parent. Wire it into NewBrandModal with a stable, spacious dialog and refresh the related newBrand/brandPicker copy across all 18 locales. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): enhance brand extraction with deterministic seed harvesting Introduce a new `seed-fallback` module to provide a server-side deterministic palette and typography seed during brand extraction. This ensures that the brand kit's initial display includes a harvested logo, an approximate color palette, and font families, improving the user experience by reducing the all-skeleton appearance during the first paint. Update the `startBrandExtraction` function to utilize this new module, allowing for a more seamless and visually appealing brand extraction process. Additionally, enhance the `BrandReferencePicker` component to reflect loading states and errors during brand extraction, ensuring users receive immediate feedback on their actions. Update related tests to verify the idempotency of the `finalizeBrand` function, ensuring that re-finalizing a brand correctly reuses the existing design system without duplication. * feat(brand-extract): enhance BrandReferencePicker and localization updates Updated the BrandReferencePicker component to reflect loading states and errors during brand extraction, improving user feedback. Added a new localization key for the brand extraction process and updated existing translations in English, Simplified Chinese, and Traditional Chinese to enhance clarity and user experience. Additionally, introduced new styles for better interaction with brand assets in the brand kit template. * feat(brands): wire in-page lightbox/masonry/asset preview + refine seed Brand-kit preview improvements for the live extraction kit: - brand-kit.html: add in-page overlay system (sandboxed iframe has no top-nav) — clickable image lightbox with prev/next, a "view all" masonry modal, and a full-page asset preview modal that loads system/artifacts/<kind>.html in an iframe. Defer auto-reload while an overlay is open so it never yanks the modal out mid-interaction. - seed-fallback.ts: prefer vivid mid-luminance hues for the seeded accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.) from the typography seed so specimens never render glyph soup. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(brands): wire in-page lightbox/masonry/asset preview + refine seed Brand-kit preview improvements for the live extraction kit: - brand-kit.html: add in-page overlay system (sandboxed iframe has no top-nav) — clickable image lightbox with prev/next, a "view all" masonry modal, and a full-page asset preview modal that loads system/artifacts/<kind>.html in an iframe. Defer auto-reload while an overlay is open so it never yanks the modal out mid-interaction. - seed-fallback.ts: prefer vivid mid-luminance hues for the seeded accent/accent-secondary, and drop icon/symbol faces (Remix Icon etc.) from the typography seed so specimens never render glyph soup. Co-authored-by: Cursor <cursoragent@cursor.com> * i18n(web): add brandPicker.opening across remaining locales + picker test Completes the brand-reference picker i18n key that was committed only for en/zh-CN/zh-TW, so every locale satisfies the typed Dict, and lands the BrandReferencePicker extraction-feedback test left untracked by the concurrent worker. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(EntryShell): enhance AMR cloud card visibility post-detection Updated the EntryShell component to ensure the AMR cloud card remains visible after detection settles, even when the AMR runtime is unavailable. This change prevents the card from disappearing and allows it to degrade gracefully to fallback content and sign-in flow. Additionally, added tests to verify the new behavior, ensuring a better user experience during onboarding. * feat(library): OD Library asset registry + OD Clipper extension Add a global, cross-project asset registry (OD Library) and a Chrome MV3 capture extension (OD Clipper), wiring the full HTTP + CLI + Web UI three-track loop per specs/od-clipper.md. - contracts: LibraryAsset/Source/Kind, ingest, search, pairing, task DTOs - daemon: 6 additive SQLite tables, content-addressed owned storage, the idempotent registerLibraryAsset hook (hash dedup + append-source), programmatic enrichment (mime/size/image dims/domain/tags), pairing tokens with a persisted extension-origin allowlist, /api/library/* routes, and /api/tools/library/{search,apply} for in-task agent reuse - cli: `od library list|get|rm|search|import|pair` - web: Library tab (grid, source badges, filters, search, live SSE updates, extension pairing affordance) - clipper/: standalone MV3 extension (background SW, content toolbar, popup) - skills/library-curator: utility skill for agent-driven asset reuse Origin middleware now honors paired chrome-extension:// origins (seeded from SQLite on boot) and exempts the pairing-confirm handshake. Enrichment AI stages (caption/OCR/embedding) are recorded as skipped pending a configured model. * feat(brands): programmatic-first design system extraction + rename Make brand extraction two-phase so a usable design system is ready the moment the user enters a URL — the instant "aha" — instead of waiting on the AI agent: 1. PROGRAMMATIC-FIRST (synchronous): startBrandExtraction now harvests the site deterministically (logo, palette, typography, one-line description, cover imagery, source URL) via prefetchBrand, synthesizes a valid design system with brandFromMaterial (no LLM), and finalizes + registers it before returning. finalizeBrand is refactored into a reusable finalizeBrandCore shared by both the programmatic path and the agent path. 2. ASYNC AI ENRICHMENT: the seeded agent prompt is reframed to enrich the already-usable design system and re-finalize in place (same user:<id>), updating every artifact/template. Bounded + best-effort: a blocked/unreachable origin skips phase 1 and stays `extracting` for the agent to drive. Gated on userDesignSystemsRoot so the legacy agent-only path stays intact for tests. Also rename the user-facing "Brand Kit" surface to "Design System" across en + zh-CN strings, project names, and the enrichment prompt. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(library): enhance asset import and management features - Updated the `import` command to allow multiple local files and remote URLs, with restrictions on supported formats. - Added new commands: `apply` for copying assets into project design files, `edit-as-page` for converting HTML assets into editable projects, and `figma` for exporting Figma captures. - Introduced sidecar functionality for storing derived data alongside owned assets, including Figma capture IR and element HTML. - Enhanced server configuration to support larger ingest payloads for asset captures. - Improved error handling and user feedback during asset import and application processes. * feat(asset-management): enhance asset dropzone and introduce chat-to-design feature - Updated the DesignSystemAssetDropzone component to improve file preview handling with new functions for creating and revoking object URLs. - Adjusted CSS for better layout and spacing in the asset dropzone. - Added a new "Chat to design" button in the LibrarySection component, allowing users to send selected assets to the Home chat composer for project creation. - Updated localization strings across multiple languages to reflect changes in asset import terminology. - Enhanced the HomeView component to handle asset staging from the chat composer. * feat(library): enhance asset application with element markup support - Updated the `applyLibraryAsset` function to include an `includeElement` option, allowing the capture of element markup alongside assets. - Modified related components (e.g., `ChatComposer`, `LibrarySection`, `FileWorkspace`) to handle the new element markup feature, ensuring both asset paths and optional element paths are returned and processed. - Introduced a new function, `fetchLibraryAssetElementHtml`, to retrieve the captured HTML for element-pick assets. - Enhanced the UI to display element markup inline within the chat composer, improving user interaction with captured elements. - Updated API contracts to reflect changes in asset application responses, including optional element markup paths. * feat(library): enhance asset filtering and preview handling - Updated the LibraryPicker and LibrarySection components to implement a badge-aware kind filter, allowing for more precise asset filtering based on badge kind. - Introduced a new `matchesKindFilter` function to streamline the filtering logic across components. - Enhanced the DesignSystemAssetDropzone to ensure proper handling of image previews, addressing issues with broken thumbnails under React StrictMode. - Added CSS styles for kind badges to improve asset representation in the UI. - Implemented tests for the DesignSystemAssetDropzone to ensure correct preview lifecycle management. * feat(library): hydrate single asset on SSE ingest Add fetchLibraryAsset(id) so the Library grid can merge just the one asset an `ingest` SSE event references instead of refetching the whole list on every capture. Returns null on miss/error. * feat(clipper): richer in-page image picker Collect CSS background-image url()s in addition to <img> (so hero/section art painted as backgrounds is no longer silently missed), defer thumbnail decode to visible cells via IntersectionObserver, draw downscaled canvas thumbnails instead of second full-res decodes, and add locate-on-page highlighting so a picked image can be traced back to its DOM source. * feat(library): implement lazy loading for thumbnails and enhance asset filtering - Introduced a `LibraryThumb` component to lazily load heavy content (images, videos, iframes) only when they are near the viewport, improving performance. - Added a debounced search feature to optimize asset filtering, reducing unnecessary network requests during rapid input. - Enhanced the asset filtering logic to track active filters using a ref, ensuring efficient updates during live events. - Updated the `snapshotCardRects` and `cardIdsInBand` functions to support improved hit-testing for drag-and-drop interactions. * feat(library): lazy picker thumbnails + debounced search Extend the Library grid's lazy-thumbnail + 250ms debounced-search pattern to the composer LibraryPicker so opening it no longer fires one full-bytes request per asset, and tidy the clipper content-script image collection. * feat(clipper): compress and budget capture inlining Re-encode large raster images to downscaled WebP and inline smallest-first within a fixed budget, dropping only the secondary Figma IR past a safe body size, so an image-heavy page (e.g. a news front page) always saves as an editable HTML capture instead of 413-failing the ingest. * test(library): LibraryPicker debounce + lazy-thumbnail coverage Cover the composer picker's 250ms debounced search and its lazy <img> mount (deferred until the card is in view), matching the grid's perf test. * feat(design-system): enhance asset handling and UI for design systems - Updated the CLI to support additional asset kinds, including 'design-system'. - Enhanced the DesignSystemProvenance type to include source URLs, improving provenance tracking. - Modified the design system generation jobs to correctly summarize source links and GitHub repositories. - Updated UI components to reflect changes in asset handling, including new source link management in the DesignSystemFlow. - Improved tests to cover new functionality for adding source links and ensuring proper handling of design system assets. * refactor(library): rename 'design-system' to 'brand kit' and enhance thumbnail loading - Updated labels and filters in Library components to replace 'design-system' with 'brand kit'. - Introduced a shimmer skeleton for lazy-loaded thumbnails in the LibraryPicker to improve user experience during asset loading. - Enhanced the PickerCard component for better performance by memoizing individual asset cards. - Updated tests to ensure proper handling of brand kit assets and their visibility in the LibraryPicker. * feat(clipper): implement internationalization for toolbar and popup - Added i18n support to the clipper, enabling localization of UI elements and tooltips. - Introduced a new i18n.js file to manage translations for various languages. - Updated content.js and popup.js to utilize the i18n functions for dynamic text rendering. - Enhanced accessibility by ensuring aria-labels and tooltips are also localized. - Improved user experience by providing localized messages for actions and statuses. * feat(clipper): enhance brand kit extraction and localization support - Updated the brand kit extraction process to include improved handling of assets and localization for various UI elements. - Added internationalization support for the brand kit feature, allowing for dynamic text rendering based on user locale. - Enhanced the user experience by ensuring that all relevant messages and tooltips are localized. - Updated tests to cover new localization features and ensure proper functionality of the brand kit extraction process. * feat(clipper): enhance brand color derivation and update localization - Introduced new functions for color manipulation, including linear interpolation and clamping, to improve brand color derivation. - Updated the deriveBrandColors function to better map observed palettes to semantic roles, ensuring consistent brand representation. - Revised localization strings in i18n.js to reflect changes from 'brand kit' to 'design system', enhancing clarity and user experience. - Improved overall code organization and readability by refactoring existing functions and adding new utility methods. * refactor(clipper): update terminology from 'brand kit' to 'design system' - Replaced all instances of 'brand kit' with 'design system' across various components and localization files for consistency. - Updated UI elements, tooltips, and documentation to reflect the new terminology. - Enhanced user experience by ensuring clarity in the design system extraction process and related functionalities. - Adjusted localization strings in multiple languages to align with the updated terminology. * feat(clipper): enhance image fill handling and normalization - Introduced functions to normalize image fills by converting non-PNG/JPEG formats (SVG, WebP, GIF, AVIF) to PNG before import, ensuring all images are properly rendered in Figma. - Updated the UI to report the number of images converted and dropped during the import process, improving user feedback. - Enhanced the overall image processing workflow to prevent silent failures when unsupported formats are encountered. - Revised documentation to reflect the new image handling capabilities and supported formats. * feat(clipper): enhance UI kit and busy state feedback - Updated the UI kit to include new components such as inputs, selection, and overlays, improving the overall design system representation. - Enhanced the busy state feedback during capture processes with localized messages and a step-by-step progress indicator, providing users with clearer status updates. - Revised localization strings to support new UI elements and improve user experience across multiple languages. - Improved documentation to reflect changes in the UI kit and busy state handling. * fix(brands): restore design-systems nav entry + reconcile BrandsTab on re-activation Address review feedback on PR #4260: 1. EntryNavRail dropped the only control that reached view==='design-systems' when Brands replaced it in the rail, leaving the still-rendered/routed design-system manager deep-link only (the entry-nav-design-systems e2e specs assert this). Restore a reachable rail entry (blocks icon, existing navDesignSystems key) alongside Brands. 2. BrandsTab only fetched once on mount, but EntryShell keeps sub-views mounted and toggles visibility, so a brand finishing extraction in its backing project never reconciled until a full reload. Refresh whenever the Brands view becomes active again, and poll while any brand is extracting (torn down once settled / when hidden). Red spec: tests/components/BrandsTab.refresh.test.tsx (fails pre-fix: fetchBrands called once, not twice). * Update clipper/brand-capture.js * fix(clipper): improve busy state handling and UI feedback - Adjusted the spinner CSS to use flex properties for better layout control. - Enhanced the reclampIfMoved function to preserve user position during busy state transitions. - Added loading toast notifications for popup-launched captures to ensure progress visibility even when the on-page bar is hidden. * feat(daemon): add kiwi-schema dependency and enhance Figma API integration - Added kiwi-schema package to the daemon for improved schema handling. - Updated FigmaApiNode interface and related functions to support shared functionality with the offline decoder, ensuring consistency in node processing. - Refactored capture functions in clipper to maintain UI visibility during DOM/IR snapshots, enhancing user experience during capture operations. * fix(web): surface missing backing projects * fix(web): re-enable brand actions after use * fix(daemon): serve brand logos from data roots * fix(brands): reconcile failed extractions * feat(daemon): implement offline Figma import and decoding functionality - Added support for importing `.fig` files directly into the daemon, enabling offline processing without requiring a Figma account. - Introduced a new `fig-decode.ts` module for decoding `.fig` files, handling both ZIP-wrapped and raw formats. - Created `figma-import.ts` to orchestrate the import process, generating a canonical snapshot and associated metadata. - Enhanced the server to handle Figma file uploads and integrate with the new decoding logic. - Updated package dependencies to include `kiwi-schema`, `html2canvas`, and `jspdf` for improved functionality. - Added tests for the new Figma import features to ensure reliability and correctness. * feat(clipper): reload-proof capture progress badge on the extension icon The on-page progress strip dies if a page reloads itself mid-capture (aggressive paywall sites like economist.com do this), leaving no loading signal. Add a per-tab '•••' badge on the extension icon for the lifetime of any capture message — it lives on the action icon, so a page navigation can't take it down. Verified end-to-end via a real loaded extension. * feat(daemon): add export functionality for Figma and enhance PDF export process - Introduced `runFigma` command for importing Figma designs, supporting both local `.fig` files and Figma URLs. - Added detailed usage instructions for the `od figma import` command. - Implemented `runExport` command for programmatic export of HTML/deck artifacts to PDF, PPTX, or image formats. - Enhanced error handling and user feedback during export processes. - Removed obsolete `build-pptx-export-prompt` module and related tests to streamline the codebase. * feat(daemon): enhance library synchronization and export capabilities - Implemented `reconcileLibrary` to mirror design systems and agent-produced project deliverables into the Library as referenced assets. - Added support for programmatic export of artifacts via the `od export` command, including detailed usage instructions. - Introduced new functions for handling Figma imports and exports, improving integration with design workflows. - Enhanced error handling and user feedback during synchronization and export processes. - Added tests for new features to ensure reliability and correctness. * feat(web): PPTX export for any shareable artifact + Library toolbar tooltips * chore(nix): refresh pnpm deps hash * refactor(web): enhance onboarding view and file export progress indicators - Updated the onboarding view layout for improved accessibility and visual hierarchy, including adjustments to spacing, typography, and button styles. - Introduced a loading toast for file export progress, displaying elapsed time and estimated time remaining for slide exports. - Added new translation keys for export progress messages in multiple languages. - Refactored the export progress handling to provide real-time updates during the export process, improving user feedback and experience. * refactor(web): streamline export capture bridge and update connector styles - Removed unused loading logic for html2canvas in the export capture bridge, simplifying the code. - Updated CSS for the onboarding view connector to improve visual clarity and ensure it does not overlap with node numbers. * refactor(web): remove html2canvas dependency and enhance Figma URL handling - Removed the html2canvas package from the project, including its references in the lock file and related components. - Added functionality to manage Figma URLs within the Design System flow, allowing users to add, remove, and validate Figma file links. - Improved drag-and-drop handling to prevent unintended file navigation when dropping files outside designated areas. - Updated UI components to accommodate new Figma URL features, enhancing user experience and accessibility. * refactor(web): unify brand and design system flows - Merged the brand extraction process into the design system creation workflow, allowing users to start from a brand directly within the design system wizard. - Updated routing to redirect legacy brand links to the unified design systems tab. - Enhanced the onboarding experience by removing the separate Brand Kit tab and integrating brand selection into the design system creation process. - Improved UI components to reflect these changes, ensuring a seamless user experience across the application. * feat(web): introduce brand enrichment banner and picker modal - Added a new BrandEnrichmentBanner component to allow users to refine programmatically-extracted design systems with AI by selecting design-system skills. - Implemented a BrandPickerModal for selecting brands from a searchable gallery, enhancing the design system creation flow. - Updated ChatPane to conditionally display the enrichment banner for eligible brand projects, improving user engagement. - Enhanced the design system flow to support the new brand enrichment features, ensuring a seamless experience for users. * feat(web): enhance BrandPickerModal and DesignSystemAssetDropzone - Updated the BrandPickerModal to allow scrolling of the entire picker area, improving user experience by creating a unified scrolling surface. - Added new props to the BrandReferencePicker for action labels and scroll root reference, enhancing flexibility in brand selection. - Introduced a new DesignKitView component for rendering design kits consistently across different surfaces. - Enhanced the DesignSystemAssetDropzone to support a wider variety of file types with appropriate previews, improving asset management during design system creation. - Updated styles for better visual clarity and responsiveness across components. * feat(web): update Design Systems tab actions and enhance localization - Changed the button label in the DesignSystemsTab from "Edit" to "Open" for better clarity in user actions. - Added a new translation key for 'dsManager.openSystem' across multiple languages to support the updated button label. - Enhanced the FileWorkspace component to ensure the Design Files tab aligns correctly with the Design System tab, improving UI consistency. - Implemented a new design system editing feature that allows users to fetch and save design system content from DESIGN.md, enhancing the design workflow. * fix(merge): repair post-merge regressions after origin/main integration Follow-up fixes on top of the origin/main merge ( |
||
|
|
ed1df234ad |
fix(ci): needs-validation gate must resolve the queued PR from the merge_group ref (#4742)
* fix(ci): resolve merge_group PR from the queue ref, not commits/<sha>/pulls The needs-validation merge gate resolved PRs in the merge_group via commits/<sha>/pulls. The merge queue squashes, so the queued commits are NEW synthetic SHAs that GitHub does not associate back to the PR — the lookup returned no PRs, the gate logged "clear to merge", and a needs-validation PR (#4736) merged with no QA. (This was latent until the pull_request branch was removed and merge_group became the sole enforcement.) Resolve the PR number(s) from the queue ref name instead (refs/heads/gh-readonly-queue/<base>/pr-<N>-<sha>), which is stable regardless of squash. Fail closed if the ref yields no PR number. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): enumerate ALL PRs in a merge group (ref + commit subjects + commits/pulls) Addresses review: a batched merge group names only the tip entry in the queue ref, so parsing pr-<N> from the ref alone could miss earlier PRs in the batch (one could still carry needs-validation). Take the union of three sources — the ref pr-<N>, each commit subject's trailing (#N) (the queue's per-entry squash commit carries its own PR number, recovering non-tip batched PRs), and commits/<sha>/pulls (non-squash queues) — and fail closed if none resolve. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): fail closed if the compare API (PR enumeration source) fails Addresses review: the compare call feeding msg_prs/assoc_prs used 2>/dev/null || true, so an API failure silently emptied those sources while ref_prs still held the tip — the -z check passed and only the tip PR was validated, letting a batched needs-validation PR merge. Capture compare once in a REQUIRED assignment (no || true) so failure trips set -e; derive subjects + shas from that parsed JSON; make commits/<sha>/pulls a required assignment too. Only grep no-match within a successful response is tolerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d40006f70b |
fix(amr): extend ACP stage timeout (#4736)
* fix(amr): extend ACP stage timeout * fix(amr): align the outer inactivity watchdog Generated-By: looper 0.9.11 (runner=fixer, agent=codex) * fix(acp): scope 30 minute timeout to hinted runtimes Generated-By: looper 0.9.11 (runner=fixer, agent=codex) |
||
|
|
ccfdc50929 |
feat(analytics): first-call cache-hit + is_followup_turn telemetry (session-reuse baseline) (#4725)
* feat(analytics): measure first-call cache hit + is_followup_turn on run_finished
Instrumentation-first (telemetry before optimization) for the session-reuse
work: ship a v0.12.0 baseline that measures the cache-hit rate of the FIRST
model call of each turn, then compare it against the post-optimization release.
Why first-call: the existing run_finished usage scan is a REVERSE scan that
takes the LAST usage event of the turn. Within a turn, every call after the
first re-reads the growing prefix, so the last/aggregate cache-hit ratio is
high regardless of whether the session was reused — it masks the signal. The
session-reuse win shows up only on the OPENING call of a follow-up turn.
- scanRunEventsForUsageAnalytics now also forward-scans for the first usage
event and emits first_call_input_tokens / first_call_cache_read_input_tokens
/ first_call_cache_hit_ratio (anthropic vs openai cache accounting mirrored
from the existing last-call path). The group is gated on a real first-call
input so cache-only alias payloads don't emit a dangling cache_read.
- run_finished carries is_followup_turn (a prior completed assistant turn
exists) so first_call_cache_hit_ratio can be sliced to the turns where
session reuse actually applies.
- The first-call fields are reliable only for per-call-usage agents
(claude / opencode / codebuddy / pi). codex reports a single cumulative
turn.completed usage, so its first stream event is the whole-session
aggregate, not the opening call — gated out of the stream path here; its
per-call number must come from the rollout last_token_usage (follow-up).
* feat(analytics): recover codex first-call cache hit from the rollout
Completes the first-call cache-hit telemetry for codex. codex emits only a
cumulative `turn.completed` usage on the exec/json stream the daemon consumes,
so the stream cannot reveal the cache-hit of a turn's OPENING model call — the
number that moves when a session is reused. codex does record per-call usage in
its rollout JSONL (`token_count.info.last_token_usage`), so we read it there.
- codex-rollout-usage.ts: pure `extractCodexLastTurnFirstCallUsage` takes the
first `token_count` after the last `task_started` (the turn the daemon just
finished), skipping zero-input placeholder calls. Validated against real
codex 0.133.0 rollouts: a warm resume turn reads ~95-96% cache hit, a cold
open ~15-18% — the exact baseline-vs-optimized signal. `readCodexRolloutFirstCall`
locates the rollout best-effort (newest-first walk of
`$CODEX_HOME/sessions/**/rollout-*-<thread_id>.jsonl`) and never throws.
- json-event-stream.ts surfaces codex's `thread.started` thread_id on the same
`sessionId` status channel claude uses, so run_finished can find the rollout
(and a future native-resume path can reuse it). `codexSessionIdFromRunEvents`
recovers it from the stored run events.
- run_finished now resolves first-call usage per agent: codex from the rollout,
every other agent from the stream forward-scan, behind one `firstCallUsage`
object. CODEX_HOME comes from the drift-proof `spawnEnvForAgent('codex')`
resolver so it honors sandbox relocation.
* fix(analytics): default codex rollout home to ~/.codex when CODEX_HOME unset
End-to-end testing through a real daemon surfaced this: codex's first-call
fields were silently dropped on every non-sandboxed run. The daemon only sets
CODEX_HOME when sandbox mode relocates it; with sandbox off, `spawnEnvForAgent`
returns an empty CODEX_HOME, codex itself falls back to ~/.codex and writes the
rollout there — but readCodexRolloutFirstCall bailed on the empty value instead
of mirroring codex's `$CODEX_HOME || ~/.codex` resolution.
Verified against a real daemon-driven codex run: the emitted run_finished now
carries first_call_input_tokens / first_call_cache_read_input_tokens /
first_call_cache_hit_ratio alongside is_followup_turn. Adds hermetic tests for
the locate-and-read layer (plant a rollout under <home>/sessions/.../, read it
back by session id) which previously had only the pure extractor covered.
* fix(analytics): harden first-call telemetry per release-readiness review
Adversarial review of the first-call cache-hit telemetry before a release
cherry-pick surfaced four fixable items; all are read-only/additive and keep
the feature best-effort:
- Contain throws in the codex run_finished branch. spawnEnvForAgent /
agentCliEnvForAgent ran outside any local guard, so a throw there bubbled to
the outer run_finished `.catch` and would have dropped the ENTIRE completion
event for that run (not just the codex first-call fields). Wrap them so a
failure degrades to "no first-call fields".
- Match the anthropic first-call denominator to the last-call one: include
cache_creation_input_tokens in the effective-input sum. Previously first-call
omitted it, skewing the very cross-turn comparison the metric exists for.
Added a regression test with non-zero cache_creation.
- Bound the codex rollout locator. On a miss (session captured but file
relocated/deleted) it walked the user's entire codex history; cap the scan to
the newest few day-dirs since the run's own rollout is always recent.
- Declare first_call_* and is_followup_turn on RunFinishedProps in
packages/contracts so the analytics shape is typed at the boundary.
* refactor(analytics): share one cache-token extraction for first/last-call
Review follow-up: the first-call cache-creation extraction only matched two of
the aliases the last-call path accepts (it missed cache_write_input_tokens,
cache_creation_tokens, and nested cache_creation.input_tokens), so a provider
emitting one of those on the opening usage event would compute
first_call_cache_hit_ratio with too small a denominator and overstate the cache
hit — the exact baseline metric this PR adds.
Extract one `extractUsageCacheFields` helper carrying the full provider alias
matrix and route BOTH the last-call reverse scan and the first-call forward
scan through it, so the two effective-input denominators can never drift. Adds
a fixture using the nested `cache_creation.input_tokens` alias to lock it.
|
||
|
|
009ff6566f |
[codex] Fix onboarding brand action layout (#4702)
* Fix onboarding brand action layout * Match browser tab width to file tabs * Restore release tool script mode --------- Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
e899fb93fe |
feat(packaged): main-process telemetry for pre-daemon startup failures (#4696)
* feat(packaged): main-process telemetry for pre-daemon startup failures The daemon hosts the PostHog client, so when daemon/web sidecars die before reporting status (issue #4638: better-sqlite3 vanished from the bundle -> ERR_MODULE_NOT_FOUND), nothing emits telemetry and the whole pre-daemon crash class is invisible on every dashboard. Add a main-process direct capture on the fatal-exit path: classify daemon-vs-web by log-path segment, parse the real error code + missing module from the sidecar log tail, scrub home dirs, and POST one packaged_runtime_failed event over plain fetch (no posthog-node SDK, no new dependency). Zero happy-path side-effects by construction: all logic runs only inside main().catch, fetch is wrapped in Promise.race with a hard timeout, errors are swallowed, and it is a no-op without a PostHog key. Crash telemetry follows the existing captureSafety policy (stability data is retained even for opted-out users); Settings -> Privacy copy follow-up tracked in the PR. Slice B of the crash-governance spec (PR #4694). * fix(packaged): Windows web-path classify + main-process installationId (review) Address PerishCode review on #4696: - classifyStartupFailure normalizes backslashes before the /web/ segment check so Windows web-sidecar failures aren't misreported as daemon-start. - resolveStartupDistinctId now takes an explicit installationRoot (threaded from paths.installationRoot); OD_INSTALLATION_DIR is only set in the daemon child env, so the main process previously always fell back to the synthetic id. Adds a Windows web-path fixture and an installationId-resolution test (18 total). * fix(packaged): raise crash-telemetry flush timeout 1.5s -> 5s (e2e finding) Real-machine e2e (local packaged build -> delete better-sqlite3 to reproduce issue #4638 -> launch -> query PostHog) showed the 1.5s cap silently DROPPED the event: a cold DNS+TLS handshake to us.i.posthog.com from a fresh main process exceeds 1.5s, so Promise.race timed out and process.exit(1) killed the in-flight POST. Confirmed with a standalone repro (1.5s never landed, 6s did). 5s lets the single request complete on a cold connection; an offline machine still exits fast because fetch rejects on DNS failure (send resolves early), so only a black-hole network pays the full bound — acceptable on a crash exit. * feat(packaged): stamp shared safety-event envelope on crash telemetry (review) Address PerishCode review on #4696: the direct PostHog emitter only sent the startup props + $os, omitting the envelope captureSafety always stamps. Mirror it so env/schema-filtered dashboards bucket packaged_runtime_failed beside the rest of the stream: event_schema_version, env, device_id, client_type (packaged_main), capture_source (packaged/startup), ui_version, event_id and $insert_id (dedup key). EVENT_SCHEMA_VERSION + the env resolver are replicated (packaged doesn't depend on contracts) with lockstep comments. Adds a typed PackagedRuntimeFailedProps + AnalyticsEventPayload entry in contracts, and a test asserting the envelope wire fields (19 tests). * fix(contracts): restore packaged_runtime_failed in AnalyticsEventName (review) The name union member was clobbered when the envelope change synced events.ts; only the AnalyticsEventPayload entry survived. PerishCode caught the regression — restore the canonical AnalyticsEventName member so the event name and its typed payload stay in lockstep. |
||
|
|
eef6efa5c7 |
[codex] show AMR wallet balance in Open Design (#4675)
* feat: show AMR wallet balance * fix: preserve AMR wallet reauth state * chore: retrigger PR validation * fix: bound AMR wallet balance fetch * chore: retrigger wallet validation * chore: bump bundled Vela CLI to 0.0.18 * chore: retrigger Vela CLI validation * chore(nix): refresh pnpm deps hash |
||
|
|
b2dee738b7 |
feat(desktop): update startup splash to capitalised "Open Design" wordmark (#4721)
Swap the packaged-app startup splash animation for a re-cut master whose wordmark reads "Open Design" (was "Open design"). The clip is inlined into the desktop main bundle as a base64 data: URL (unchanged mechanism — the splash shows before any HTTP server is up), now as transparent VP9/WebM so it composites over the existing light splash background. - apps/desktop/src/main/splash-video.ts: regenerated from the new asset, inlined verbatim (no transcode) to preserve authored quality; Electron's bundled Chromium plays VP9 (incl. alpha) from a data: URL natively. - apps/web/public/startup-animation.webm: refresh the source master to match. |
||
|
|
5360b5a4e9 |
fix(ci): enforce the needs-validation merge gate only in merge_group, keep the PR check green (#4714)
The needs-validation hard gate failed the validate job (Validate workspace) on pull_request, turning the PR's own required check red. A deliberately-red required check is indistinguishable from a real CI failure downstream: it flips mergeStateStatus to BLOCKED and reads as ci_status=failure to the review bot, which deadlocks the very QA/validation meant to clear the label — QA is never requested, the label is never removed, the PR is blocked forever. Move the gate to the merge_group (merge-queue) context only. main requires the merge queue, so every merge still passes through merge_group and a needs-validation entry is ejected there and can never land — but the merge_group run executes on the queue's transient ref, so its failure never appears in the PR head's status rollup. The PR's Validate workspace stays green, QA runs, removes the label, and only then does the queue let it through. No bot/charter change needed. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
295e541085 |
fix(e2e): stop the packaged Windows onboarding smoke racing the onboarding redirect (#4710)
A fresh install boots at od://app/ and the SPA immediately redirects to the dedicated onboarding route (od://app/onboarding, since the #4513 cloud sign-in redesign). The healthy inspect can land just before or just after that redirect, so the reported URL/href is either one — but the smoke pinned the exact bare root with .toBe('od://app/'), which flaked ~3 of 4 nightly Windows builds when the redirect won the race (AssertionError: expected 'od://app/onboarding' to be 'od://app/'). Match the prefix leniently, exactly as mac.spec.ts and this test's own onboarding-landing assertion already do. Test-only; no product change. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f4fe5ad757 |
feat(ci): implement the plugin-preview bake pipeline (spec 4548) (#4700)
* feat(ci): bake pipeline slice 1 — previews-diff guard + single rolling PR Per specs/change/20260618-plugin-preview-bake-pipeline/spec.md rollout step 1 (the smallest change that stops the bleeding on the stacked bake-PR backlog). - scripts/plugin-previews-diff.mjs: decide whether a manifest's `previews` subtree changed, ignoring the per-run `generatedAt` timestamp. node:test coverage in plugin-previews-diff.test.mjs (the #4261 timestamp-only noise case, entry change, add/remove, key-order stability). - bake-plugin-previews.yml: open a review PR only when `previews` actually changed (was: whole-file `git diff` that fired every run because of generatedAt), and reuse ONE rolling branch (chore/plugin-previews) / force-update the open PR in place instead of stacking chore/plugin-previews-<run_id> per run. - guard.ts: allowlist the two new CI-only .mjs scripts. Deferred to later slices (same spec): pre-merge same-repo coupling job, release-cut full bake, tag-union GC, directory-layered artifact keys. * feat(ci): bake pipeline slices 2-4 — pre-merge coupling, release full bake, GC, directory keys Completes the spec (specs/change/20260618-plugin-preview-bake-pipeline) on top of slice 1, all in one PR (GC ships dry-run so staged ENABLEMENT still holds): - .github/actions/bake-previews: composite action for the shared render + R2 upload core, so the three bake workflows stop duplicating it. - bake-plugin-previews.yml (post-merge): refactored onto the composite; role is now uploader + fork path + nightly backstop (rolling PR unchanged). - bake-plugin-previews-pr.yml (slice 2): pre-merge bake for SAME-REPO PRs — renders, uploads, and commits the manifest INTO the author's branch so it rides with the code change. Loop guard checks the head COMMIT author (git log -1 %ae of head.sha, fetched via full checkout), not head.user.login, plus a no-op previews-diff guard. Forks fall through to post-merge. - bake-plugin-previews-release.yml (slice 3): release-cut full bake committing the authoritative manifest onto release/**, with paths-ignore + bot-author loop guards. - scripts/bake-plugin-previews.mjs (slice 4a): directory-layered, content- addressed keys <id>/<fingerprint>/preview.mp4 (+ poster.jpg), prefix-relative in the manifest. Daemon consumer already resolves base+key / path.join(dir,key) so no consumer change; reused flat entries are left untouched (additive). - scripts/plugin-previews-gc.mjs (slice 4b) + .github/workflows/ bake-plugin-previews-gc.yml: weekly R2 GC. Protected set = keys referenced by every tag + live release/** HEAD + main; deletes orphans older than a 90d grace window. DRY-RUN by default (needs --delete AND GC_ENABLE_DELETE=1). Pure protected-set/orphan logic covered by node:test. - guard.ts: allowlist the new CI-only .mjs files. * fix(ci): capture diff-guard result on its own line so a helper error fails the step Per review: `if [ "$(node plugin-previews-diff.mjs ...)" != changed ]` swallows the helper's exit 2 (bad args / unreadable manifest) inside command substitution, so an error reads as empty string → 'unchanged' branch → the manifest PR/commit is silently skipped despite a successful bake. Capture into diff_result on its own line (so `set -e` aborts on a helper error) and `case` on the value, treating unexpected output as a workflow failure. Applied to all three bake workflows. * fix(ci): satisfy actionlint — quote gc description colon + route PR/dispatch context through env - bake-plugin-previews-gc.yml: quote the `delete` input description (the '(default: ...)' colon broke YAML parsing) and pass dispatch inputs via env (GC_DELETE/GC_GRACE_DAYS) instead of interpolating into the run body. - bake-plugin-previews-pr.yml: route head.sha/head.ref through HEAD_SHA/HEAD_REF env vars to avoid the script-injection lint on untrusted PR context. * fix(ci): hard-gate the release bake job to release/** branches workflow_dispatch can fire from any ref; the commit step pushes the authoritative manifest to the triggering ref with contents:write, so a dispatch on main would write straight to main and bypass the release back-merge. Add a job-level `if: startsWith(github.ref, 'refs/heads/release/')` guard. * fix(ci): GC fails closed on partial protected-ref data Per review (non-blocking but real once deletion is armed): the GC workflow's protected-ref fetches ended with '|| true', so a transient fetch failure could leave the tag/release/main protected set incomplete and, with GC_ENABLE_DELETE=1, prune clips a live release/main still references. Drop the '|| true' (fail the job if the protected refs can't be fetched), and add a script-side guard that refuses to delete when the protected set is empty or origin/main's manifest is unreadable. |
||
|
|
254fd7ddf0 |
fix(ci): only auto-merge a backport that is a pristine bot cherry-pick (#4705)
* fix(ci): only auto-merge a backport that is a pristine bot cherry-pick The auto-approve/merge premise is that a backport is the untouched cherry-pick of code already reviewed on main — so it can skip a fresh review. That breaks the moment a human pushes a commit (resolving a conflict, fixing a failing test): those commits were never reviewed, yet the existing gates don't catch them — the PR author stays the bot, a draft gets un-drafted, and head==CI-sha only guards post-green races, not bot-vs-human authorship. Gate auto-approve + auto-merge on pristine == 'true': every commit on the branch was committed by github-actions[bot] (what korthout's cherry-pick produces; the human author is preserved but the committer is the bot). Any human commit -> committer != github-actions[bot] -> pristine=false -> skip, and the PR falls back to human review (REVIEW_REQUIRED; release-gate blocks the release until it lands). Fail closed: any lookup miss / empty list -> not pristine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): paginate the pristine-commit check so a >100-commit backport can't pass on its first page per_page=100 without --paginate only validated the first 100 commits: a human commit at position 101+ would be missed and the PR would auto-merge unreviewed code (fail-open). Use --paginate to fold in every commit and fail if any committer is not github-actions[bot]; -F/-x match the literal bot login (the brackets are not a regex class). Fail closed on an empty list / lookup miss. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): use portable grep -c count for the pristine check, not grep -qv `! ... | grep -Fqvx` mis-evaluated to pristine=true on a human-touched branch under BSD grep (grep -q's early-exit status is unreliable across BSD/GNU) — the opposite of safe. Count the non-bot committer lines with grep -Fvxc, whose count semantics are portable, and require zero. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): fail closed when paginated commit lookup partially fails gh api --paginate can emit several pages and then fail on a later one; `|| true` kept that partial output, so an all-bot first page could read pristine=true without ever inspecting the commits on the failed page. Capture gh's exit status with `if !` and blank the list on any failure, so pristine requires a complete, successful enumeration of every commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(ci): assert the pristine check's actual shape (paginate + grep committer count) The topology assertion still referenced the old in-jq comparison '(.committer.login // "") == "github-actions[bot]"', which no longer exists after the check moved to extracting '.[].committer.login // "none"' and counting non-bot committers with grep -Fvxc across --paginate'd pages. Assert the shape that exists now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cbf7e881fa |
fix(ci): match the App author login gh actually returns (app/open-design-release-bot) (#4703)
The auto-approve/merge gates checked author == 'open-design-release-bot[bot]', but `gh pr view --json author --jq .author.login` returns 'app/open-design-release-bot' for an App-authored PR. The gate never matched, so every clean backport's approve + merge steps were skipped and the PR sat unmerged (e.g. #4697, #4698 — CI green, no approval). Match the actual value in all three gates + the topology test. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
dab92a8cf2 |
fix(analytics): stamp frame platform + drop fetch-failure noise in exception transport (#4661)
* fix(analytics): stamp frame platform + drop fetch-failure noise in exception transport The hand-rolled browser-exception transport in error-tracking.ts (the sole $exception source since client.ts sets capture_exceptions:false) had two bugs that PostHog's weekly digest surfaced as "512K exceptions / 511K failed to ingest": 1. Frames never carried `platform`. PostHog's exception ingestion requires it per frame to pick the symbolication/grouping strategy; without it every event failed processing with "missing field platform" and was dropped server-side. 100% of our ingested $exception events had platform=(missing), all from capture_source=web/error-tracking. Now stamped 'web:javascript' on every frame, matching posthog-js. 2. `TypeError: Failed to fetch` from the packaged app's daemon connection dropping (restart, boot race, navigation/unmount abort, offline) was ~90% of all captured exceptions — environmental noise, not bugs, one polling loop can emit thousands. Dropped at the capture chokepoint along with AbortError and the WebKit/Firefox network-error wordings. Also hardens the telemetry beacon: `void fetch(...)` had only a synchronous try/catch, so a failed beacon rejected unhandled and got re-captured as its own Failed to fetch — a self-amplifying loop. Now `.catch()`-ed. Red specs in error-tracking.test.ts go red on main (frames lack platform; noise is dispatched) and green here. * fix(analytics): scope fetch-noise drop to packaged runtime, stop dropping AbortError Address review: captureException is the single chokepoint for window.error / unhandledrejection / reportHandledException, so a blanket drop of `Failed to fetch` would also suppress a real broken-/api/* or CORS/TLS signal from the normal web app — and blanket-dropping AbortError was never justified by the data. - Gate the fetch-failure drop on the exception originating in packaged app code (od:// stack origin), which is where the ~90% daemon-connection churn lives. The same TypeError from a web (http/https) context stays captured. - Remove the AbortError drop entirely (not a measured noise source, and not necessarily fetch-lifecycle cancellation). Test now asserts the invariant directly: packaged od:// fetch noise dropped, the identical web-origin error kept, AbortError kept. * test(analytics): cover beacon-failure self-amplification backstop Address review: add a regression that a failed telemetry beacon, surfacing as an unhandledrejection from od:// transport code, does not re-enter capture as a second $exception/beacon. Exercises the packaged-noise backstop (the loop-breaking layer observable under jsdom; dispatch()'s .catch() is a process-level promise rejection that jsdom does not route to window.onunhandledrejection, so it is not unit-observable here). * fix(analytics): recognize file:// app-bundle frames as packaged origin Address blocking review: packaged exceptions don't only arrive as od:// frames — source-mapped frames surface as file:///.../<Channel>.app/Contents/Resources/... (the shape scrub.ts rewrites). The od://-only check let those packaged fetch-failures leak through. Broaden originatesInPackagedApp to also match the .app/Contents/Resources bundle marker (channel-agnostic, so Beta/Preview builds are covered). Regression added: file:// app-bundle Failed-to-fetch stacks are dropped too; red against the od://-only check, green here. |
||
|
|
a2c294b747 |
fix(e2e): stub /api/design-systems in /projects UI P0 tests to remove merge-queue flake (#4695)
The /projects view in EntryShell gates its render on projectsLoading || skillsLoading || designSystemsLoading, and designSystemsLoading only clears once GET /api/design-systems resolves (App.tsx dsLoading). Seven tests navigate to /projects without stubbing /api/design-systems, so their first assertion waits on the real daemon's response — fast locally but a >10s CenteredLoader under CI load. This is what dequeued PR #4548 from the merge queue (UI P0 project-workspace, project-management-flows.test.ts:203 timed out twice on .designs-empty-state). Add a stubDesignSystemsEmpty helper and call it before each goto('/projects'), mirroring the existing /api/design-systems stubs used by the design-system tests in the same file. Pure test-network change; no product behavior touched. |
||
|
|
02b32e4eb7 |
docs(spec): plugin preview bake pipeline — couple bakes to source, bound R2 growth (#4548)
* docs(spec): plugin preview bake pipeline — couple bakes to source, bound R2 * docs(spec): move plugin preview bake pipeline spec under specs/change * docs(spec): fix relative AGENTS.md link after move * docs(spec): address review — destale PR refs, release-cut loop guard, GC covers nightly/preview * docs(spec): adopt PerishCode review — artifact identity contract, directory-layered keys, explicit invalidation boundary * docs(spec): fix manifest-key base authority (no double prefix) + real pre-merge loop guard * docs(spec): bot-author guard must check head commit author, not head.user.login * docs(spec): pre-merge full-bake on recipe/BAKE_VERSION changes + ensure head.sha is fetched before author check |
||
|
|
52a1cd4375 |
chore(plugin-previews): refresh baked preview manifest (#4672)
Co-authored-by: open-design-bot <bot@open-design.ai> |
||
|
|
0d473eaa38 |
Let idle MCP stdio helpers release abandoned sessions (#4658)
Codex can leave helper stdio open after a desktop connection disappears, so the Open Design MCP bridge now treats 30 minutes without MCP traffic as an abandoned session and closes its stdio transport. Request handlers are tracked as in-flight work so a long tool call cannot be killed in the middle, and the existing SDK close hooks remain chained for normal teardown. Constraint: Issue #4400 reports leaked MCP helper processes when Codex does not close stdio Rejected: Exit on a fixed wall-clock timer | would kill active long-running tool calls Confidence: high Scope-risk: narrow Directive: Keep stdin EOF and idle timeout paths flowing through transport.close so SDK teardown remains intact Tested: pnpm --filter @open-design/daemon exec vitest run tests/mcp-stdio-idle.test.ts tests/mcp-resolve-project.test.ts Tested: pnpm --filter @open-design/daemon typecheck Tested: pnpm guard Tested: pnpm typecheck Not-tested: Manual long-lived Codex Desktop multi-session leak reproduction Related: #4400 |
||
|
|
48695090f8 |
Make WSL agent setup unambiguous for MCP installs (#4655)
Windows users running agent CLIs inside WSL were landing on the native Windows guide and copying the README MCP command directly, which left Linux /usr/bin/od shadowing, daemon origin, Node ABI, and Codex config parse failures unexplained. Add a WSL2 guide and teach the Codex config normalizer to drop nested feature tables that current Codex parses as maps instead of boolean flags. Constraint: WSL agent CLIs need the same Linux environment for the wrapper, daemon, Node modules, and credentials Rejected: Docs-only workaround | would leave daemon-launched Codex runs failing on nested features tables Confidence: high Scope-risk: narrow Directive: Keep WSL-specific guidance separate from native Windows PowerShell troubleshooting Tested: pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/codex-config-normalize.test.ts Tested: pnpm --filter @open-design/daemon typecheck Tested: pnpm guard Tested: git diff --check Not-tested: Manual WSL2 end-to-end MCP install on a Windows host Related: Fixes #4648 |
||
|
|
3467bb5830 |
[codex] Adapt cards to dark mode tokens (#4674)
* fix(web): adapt new cards to dark mode * fix(web): persist rule proposal decisions * fix(web): scope rule proposal decisions Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * chore(ci): rerun validation after qa gate Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
cfb210ec63 |
[codex] Split CLI and BYOK first-artifact handoff (#4634)
* Split CLI and BYOK artifact handoff * fix(daemon): neutralize filesystem fallback text Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * fix(template): align web prototype filesystem handoff Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
35a7bde894 |
add Support for Mimo Code (#4578)
* fix: apply PR#4252 mimo runtime changes on upstream HEAD * fix: namespace mimo env hardening under MIMOCODE_* instead of sharing opencode path |
||
|
|
f94e887d65 |
docs(readme): refresh contributors wall (#4653)
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com> |
||
|
|
7f98b069ca |
docs: correct PRIVACY.md to reflect telemetry is on by default (opt-out) (#4631)
* docs: correct PRIVACY.md to reflect telemetry is on by default (opt-out)
PRIVACY.md stated telemetry is opt-in and "off by default" and that the app
"never starts sending anything before you do." The shipped behavior is the
opposite: `apps/web/src/state/config.ts` defaults `telemetry: { metrics:
true, content: true }` so fresh installs emit onboarding/ui_click events from
the first frame, and the first-run banner is a single-acknowledgement
informed-disclosure notice ("I get it"), not an opt-in gate. The daemon gate
(`if (telemetry?.metrics !== true) return`) therefore lets events flow before
any user decision.
Update the doc to describe the actual opt-out model: telemetry is on by
default, the banner is informed disclosure, and Settings -> Privacy (and the
banner footer) provide a one-click opt-out with per-category toggles. No code
change — this aligns the documentation with what ships.
* docs: clarify the consent banner footer only points to Settings
Per review on #4631: the first-run banner footer carries informational text
('Data sharing is on by default. You can turn it off any time in Settings ->
Privacy.') and does not itself expose an opt-out control — the toggle lives
only in Settings -> Privacy. Reword so the doc matches the shipped UI.
|
||
|
|
fa852014df |
feat(ci): auto-approve the release bot's own clean backport to satisfy release/v* review rule (#4682)
release/v* requires one approving review, so backport-automerge could not merge the bot's own backport PRs (an app can't approve its own PR). Approve as github-actions[bot] — a separate identity — under the SAME gates as the merge (author == release bot, same-repo, non-draft, head == the CI SHA, CI green). The diff was already reviewed + approved on main; the backport is a mechanical, conflict-free cherry-pick, so this auto-approval is scoped strictly to the release bot's verified backports, then merged with the App token. Topology test updated. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c5ec410fd3 |
fix(ci): backport on a label added after merge (#4678)
* fix(ci): backport on a label added after merge, not only at merge time The backport workflow only triggered on pull_request_target closed, so a 'backport release/vX.Y.Z' label applied AFTER a PR was already merged never created a backport PR (a common slip). Add the labeled event; the existing 'merged == true' guard still skips still-open PRs, so labelling an open PR does nothing until it merges, while labelling a merged PR now backports it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): gate the labeled-trigger backport on the labeler's write+ permission Per review (PerishCode): the new labeled path let a triage-only user trigger the App-token backport on a merged PR, racing backport-label-guard.yml (which strips unauthorized labels in a separate workflow). Check the labeler's permission inside this workflow, before checkout/ backport-action: on a labeled event, require github.event.sender to have write+ (fail closed); the closed/merge path stays gated by the merger's write access. Also only run for backport-* labels, not every label on a merged PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
92f219d20b |
feat(ci): auto-merge clean backports on green CI (+ Feishu ping on failure) (#4671)
* feat(ci): auto-merge clean backports on green CI + Feishu ping on failure release/v* is PR-protected but has no merge queue and no required status check, so neither GitHub auto-merge nor enqueuePullRequest applies. backport-automerge.yml reacts to ci.yml completing for a backport-* branch: - success + clean (non-draft) PR -> squash-merge with the App token (the push to release/* re-triggers notify-release-feishu / the nightly) - failure -> Feishu ping for manual follow-up - conflict (draft) -> left for a human; release-gate blocks the release until it lands Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): harden backport auto-merge — verify head identity + bind to CI SHA Per review (PerishCode): - Only auto-merge PRs authored by the release bot from the same repo (author == open-design-release-bot[bot] && !isCrossRepository), so a stranger can't get a branch named backport-* auto-merged with the App token. - Bind the merge to the SHA CI actually passed on: gate on head_oid == workflow_run.head_sha and pass --match-head-commit, so a commit pushed after CI went green is never merged untested (release/v* has no required status checks to catch it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): restrict backport auto-merge to PR CI runs + gate the Feishu path too Per review (PerishCode): - Only run for workflow_run.event == 'pull_request' on a backport-* branch, so a push or manual dispatch can't reach the App-token / Feishu steps. - Apply the same author == bot && !isCrossRepository gates to the Feishu failure step, so a fork / non-bot PR named backport-* can't spam the release group. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(ci): add topology coverage for the backport-automerge trusted workflow Per review (PerishCode): packaged-smoke-workflow.test.ts registers backport-automerge.yml as a ci workflow_run consumer and asserts its trust gates — workflow_run.event == pull_request, the App-token mint, author == bot && !isCrossRepository && non-draft && release/v* base, the head_oid == workflow_run.head_sha + --match-head-commit binding, and the same identity gates on the Feishu failure path — so the security posture is covered by tests, not just YAML review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): resolve the backport PR by the CI run's SHA, not just the branch name Per review (PerishCode): bind the resolved PR to the completed run — select the open PR whose headRefOid == workflow_run.head_sha (and head branch + release/v* base), so a different/newer PR on the same branch is never acted on. Topology test asserts the SHA-bound resolution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): resolve backport PR from workflow_run.pull_requests, not branch name Per review (PerishCode): use the run's authoritative pull_requests association as the source of truth — select the entry whose base is release/* at exactly workflow_run.head_sha — instead of a gh pr list --head branch-name lookup. Empty for forks (never auto-merged). Topology test updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
96fa2ef957 |
[codex] Fix onboarding brand extraction flow (#4633)
* Fix onboarding brand extraction flow * Restore onboarding finish setup label Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * Fix pending prompt auto-send hydration Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
6efac95b53 |
feat(deploy): Azure deployment support (App Service + ACI) (#3387)
* feat(deploy): add Azure deployment templates (App Service + ACI) Adds a deploy/azure/ lane so Open Design can be deployed to Microsoft Azure from the published runtime image, alongside the existing Docker Compose and Helm options. Covers the App Service + ACI scope of #1028. Two Bicep templates run the same single-port Alpine image used by deploy/docker-compose.yml and charts/open-design: - app-service.bicep: App Service for Containers with managed HTTPS, Always On, and health checks on /api/health. - aci.bicep: a single Azure Container Instances group with a public FQDN and an /api/health liveness probe. These are evaluation deployments: state lives on the container's local disk and is ephemeral. Open Design stores SQLite under OD_DATA_DIR, and SQLite needs real file locking, which the Azure Files (SMB) storage behind both App Service and ACI persistence cannot provide without corruption. App Service sets WEBSITES_ENABLE_APP_SERVICE_STORAGE=false to keep the data dir on local disk. Durable self-hosting stays on the Compose named volume or the Helm PVC. Both wire the daemon's env contract (OD_BIND_HOST/OD_PORT/OD_WEB_PORT/ OD_DATA_DIR/OD_PUBLIC_BASE_URL/OD_ALLOWED_ORIGINS/OD_API_TOKEN) and take the API token as a secure parameter so it never appears in deployment outputs. deploy-azure.sh wraps `az` to create the resource group, generate a token when one isn't supplied, deploy a lane, and print the URL. README.md documents both lanes, the ephemeral-data caveat, and the security trade-offs. deploy/tests/azure-bicep.test.ts guards the runtime contract and that the data dir is never mounted to Azure Files; when the bicep CLI is present it also compiles both templates. * docs(deploy): add ACI health-check example to Azure quick start |