Commit Graph
226 Commits
Author SHA1 Message Date
Marc ChanandGitHub c00bbc8c93 fix(desktop): keep modal controls clickable in drag regions (#1032) 2026-05-09 12:41:49 +08:00
66f84972cf feat(byok): Added Ollama CLoud as BYOK provider (#923)
* feat: add Ollama Cloud to KNOWN_PROVIDERS as OpenAI-compatible BYOK provider

* feat: add ollama.com to isOpenAICompatible base URL detection

* feat: add Ollama Cloud models to SUGGESTED_MODELS_BY_PROTOCOL fallback list

* fix: use full Ollama Cloud model list from /api/tags, drop -cloud suffix

* feat: add Ollama Cloud as native protocol with NDJSON streaming and connection test support

* fix: remove ollama.com from OpenAI compatibility check

* feat: add token overrides for Ollama Cloud models to prevent truncation

* fix: extend inferApiProtocol and legacy migration to recognize ollama.com base URLs

* fix: normalize Ollama Cloud base URL by stripping /api suffix during migration and in daemon

---------

Co-authored-by: herediaron <aronheredi346@gmail.com>
2026-05-09 11:21:16 +08:00
SidandGitHub 78ae6feb59 fix(web): surface empty-annotation state for Inspect/Picker (#890) (#1005)
When the agent emits an HTML artifact with no `data-od-id` /
`data-screen-label` annotations (a freeform PRD → HTML pass through a
Claude-Code-compatible CLI without going through a skill, for
example), the existing Inspect / Picker affordances no-oped silently:

- The bridge's click handler walks up to <html>, finds nothing tagged,
  and bails before emitting `od:comment-target` — by design, since
  posting a synthetic id here would change save-to-source semantics
  for inspect overrides (the persisted CSS keys off the same
  elementId).
- The host then sat at "Click any element with `data-od-id` to tune
  its style" — phrased as if the user just hadn't found the right
  element, when the page in fact had nothing matching at all.
- Picker mode (Tweaks → Picker) had no hint at all.

The bridge already broadcasts `od:comment-targets` with the full list
on every mode toggle and DOM mutation, but the host's existing
listener was gated on `boardMode` only — Inspect mode never learned
the artifact's annotation count.

Two surgical fixes:

1. `FileViewer.tsx`: a dedicated `od:comment-targets` listener that
   installs whenever Inspect OR Comments mode is active, mirroring
   the bridge's broadcast into `liveCommentTargets`. The
   comment-mode-only listener still owns its hover / click / pod
   events; this new listener only handles the targets list.
2. `FileViewer.tsx`: the inspect-empty-hint banner now dispatches on
   `liveCommentTargets.size === 0`. Empty: a clear "this artifact has
   no `data-od-id` annotations yet — ask the agent to add them"
   message that names the missing attribute. Populated: existing
   instructive copy. Mirrored across Inspect and Picker modes so the
   failure surface gives the same calibration signal in both.

Tests:

- `tests/runtime/srcdoc-bridge-empty-targets.test.ts` (3 cases): pin
  the bridge contract this fix depends on. Run the IIFE in jsdom and
  assert (a) `allTargets()` posts an empty list for unannotated DOM,
  (b) clicks on unannotated elements do NOT post `od:comment-target`
  (regression pin against future "synthetic id" fallbacks that would
  silently change save-to-source semantics), (c) clicks DO still
  resolve to an annotated ancestor when one exists.
- `tests/components/FileViewer.inspect-empty-hint.test.tsx` (3
  cases): pin the host dispatch — empty state in Inspect mode, the
  switch back to instructive copy when targets show up, and the
  mirrored affordance in Picker mode.

Out of scope (flagged in the design comment so it isn't lost):
- The follow-up scenario from #890 ("parent has data-od-id, target
  child does not → adjustments hit the parent") is a different bug
  that needs either synthetic-id fallback or a UI affordance to
  descend into the click target. Leaving that to a follow-up so this
  PR stays narrow.
- i18n: the existing inspect-empty-hint copy is hardcoded English;
  rolling it into the 17-locale Dict is a separate cleanup.
2026-05-09 11:20:13 +08:00
初晨andGitHub 9ef136ced5 fix: sync Orbit last run with selected prompt template (#937)
* fix(orbit): scope last run to selected template

* fix(orbit): preserve legacy last run on upgrade

* fix(orbit): pin legacy last-run fallback on refresh

* fix(orbit): pin template id at run start

* test(web): sync orbit fixtures with skill summary
2026-05-09 11:19:59 +08:00
8a8e33d31b fix(web): hide stale upload error banner when previewing other files (#994)
The upload-failure banner in FileWorkspace renders unconditionally inside
ws-body, so a failed upload's error message stayed pinned above any file
the user opened next. The banner is information about the Design Files
upload surface, not the active file viewer; once the user clicks an
existing file in the list, the message becomes unrelated stale state.

Scope the banner to activeTab === DESIGN_FILES_TAB so previewing any
file hides it. The error stays in state, so returning to the Design
Files tab brings it back: the failed upload is still unaddressed and we
do not want to silently drop that signal entirely.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-09 10:09:41 +08:00
lefarcenandGitHub afb331a288 feat: add opt-in Langfuse telemetry (#800)
* docs(specs): add langfuse telemetry change spec

Captures the design for forwarding completed agent runs to Langfuse,
including data-model mapping, field-budget caps, privacy gates,
build-secret injection, GDPR right-to-deletion approach, and the
resolved decisions on default consent, identifier shape, region, and
ownership.

* feat(daemon): add langfuse-trace module and telemetry prefs

Adds the dependency-free building blocks for forwarding completed
agent runs to Langfuse. Two layers:

- AppConfigPrefs gains installationId and a TelemetryPrefs object with
  metrics / content / artifactManifest gates. The daemon validator
  treats telemetry like agentModels — replace-on-write, drop-when-empty,
  reject non-boolean inner values.

- New langfuse-trace.ts builds a {trace-create, generation-create}
  pair from a ReportContext, capping prompt at 8 KB, output at 16 KB,
  artifacts at 50 entries, and dropping any batch larger than 1 MB
  before send. reportRunCompleted is no-op when LANGFUSE_PUBLIC_KEY /
  LANGFUSE_SECRET_KEY are unset (so dev runs and forks never emit) and
  short-circuits on prefs.metrics === false.

Server-side wiring into the run-close path lands in a follow-up.

* fix(langfuse): default to US Langfuse region

End-to-end smoke against the project's actual dev key on 2026-05-07
returned 401 from cloud.langfuse.com (EU) and 207 from
us.cloud.langfuse.com (US), confirming the org lives in US. Update the
default base URL, the matching test, and the spec's Q3 decision row to
match. Self-hosted or EU-region operators can still override via the
LANGFUSE_BASE_URL env var.

* feat(daemon): wire langfuse trace forwarding into run-close

Adds the daemon-side glue to forward completed agent runs:

- runs.ts gains an optional onTerminate hook fired once per run after it
  reaches a terminal state. Errors thrown from the hook are caught and
  logged, never propagated, so telemetry can never break the run path.

- New langfuse-bridge.ts assembles a ReportContext from the in-memory
  run record, the conversation's persisted assistant message, and the
  user's app-config preferences. It tolerates a missing message (e.g.
  when web has not yet PUT the final delta) and a missing app-config.

- server.ts stashes the original user prompt on the run object inside
  startChatRun so the bridge can include it without crossing the
  createChatRunService boundary, and registers the hook callback when
  building the run service.

Behavior remains a no-op unless LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY
are set in the daemon env AND telemetry.metrics is true in app-config.
A live smoke against us.cloud.langfuse.com on 2026-05-07 confirmed the
matching trace + generation schema is accepted (HTTP 207, both events
201 created).

* fix(langfuse): address PR #800 review feedback

P1 — Move trace forwarding off the daemon-internal run-close hook and
onto the message-persistence path. The original onTerminate hook ran
inside finish() the moment the SSE 'end' event was emitted, which is
*before* the web client's onDone handler refreshes project files and
PUTs producedFiles + final assistant content back to SQLite. Reading
SQLite at that moment routinely missed both. The fix: drop the runs.ts
hook entirely and trigger from PUT /api/projects/:id/conversations/:cid/
messages/:mid when the saved row carries a terminal runStatus. A
reportedRuns Set guards against the multiple PUT calls web makes per
turn (each retry / state update). Set entries auto-evict after the same
30 min TTL the runs map uses. Web persists a terminal-status message in
all three completion paths — onDone (succeeded), onError (failed), and
cancel (canceled) — so this catches every run shape.

P2 — postLangfuseBatch now parses the 207 Multi-Status response body.
Langfuse legacy ingestion always returns 207, and response.ok is true
for 207, so per-event validation errors used to slip through silently.
We now warn when body.errors is non-empty. Two new unit tests.

P2 — truncate() and the HARD_BATCH cap now compare UTF-8 byte length,
not String.length (which counts UTF-16 code units). A 4096-character
CJK prompt occupies 12 KB, well over the 8 KB input cap. truncate also
walks backwards to a UTF-8 leading byte so the cut never lands inside a
multi-byte codepoint. New unit test covers '设'.repeat(4096).

P2 — Spec R7 now lists the actual Langfuse trace deletion endpoint
(DELETE /api/public/traces/{traceId} for single, DELETE /api/public/traces
with body for batch). Verified by curl on us.cloud.langfuse.com:
DELETE /api/public/traces/X → 200; the path the original spec named
(POST /api/public/trace/X) returns 404. Reference link points at
langfuse.com/docs/administration/data-deletion.

P3 — Q4 (legacy ingestion vs OTel) moved from Open Questions to
Resolved Decisions. The implementation already commits to legacy and
the trade-off was discussed during design; the open-question status was
stale.

* feat(web): privacy consent surface + Settings → Privacy tab

Adds the user-facing half of the telemetry feature so the daemon-side
hook from PR #800 has something to talk to.

- AppConfig gains optional `installationId` (anonymous v4 uuid generated
  on first opt-in; null after explicit decline; undefined when the user
  has never seen the consent surface) and `telemetry: TelemetryConfig`
  ({metrics, content, artifactManifest}). syncConfigToDaemon round-trips
  both fields so the bridge module sees the same prefs.

- SettingsDialog grows a Privacy section with two states. When the user
  has never made a consent decision (typical first-run path), the
  section renders the GDPR-aligned consent card: a kicker, the disclosure
  body listing both metrics and conversation content as separate bullets,
  and two equally-prominent buttons ("Share usage data" / "Don't share").
  The Don't-share path keeps the app fully usable (core app must work
  with all tracking declined). After a decision the same panel switches
  to three independent toggles + the anonymous ID + a "Delete my data"
  button that rotates the ID and turns everything off.

- App.tsx points the welcome modal at the new Privacy section so the
  consent decision is the first thing a fresh installation sees.

- 17 i18n keys land in en + zh-CN + zh-TW with hand-translated copy,
  and as English placeholders in the remaining 14 locales — enough for
  the parity check to pass while leaving room for proper localisation
  in a follow-up. Dict type updated.

- Minimal index.css for the consent card + toggle rows so the panel is
  legible without depending on follow-up design polish.

Telemetry remains a no-op end-to-end until the user clicks Share usage
data: the daemon gate (prefs.metrics === true) keeps every code path
short-circuited otherwise.

* refactor(web): rebuild Privacy panel using project-native settings primitives

The first cut used custom .settings-privacy-* classes + raw HTML
checkboxes that didn't match any other Settings tab. Replace with the
shell other sections already use:

- settings-subsection containers with section-head + h4 + .hint
- seg-control / seg-btn pill toggles ("active" / "offline") for each of
  the three telemetry preferences, mirroring NotificationsSection
- a 2-cell seg-control for the consent card so Share usage data and
  Don't share carry identical visual weight (the GDPR equal-prominence
  requirement that the previous accent / outline split missed)
- ghost button + readonly text input for the installation id row,
  mirroring the API-key field pattern elsewhere

Drop the bespoke CSS block in favor of inheriting the existing
settings-section / seg-control / ghost styling. The only privacy-
specific style left is a tight definition list inside the consent card
for the metrics + content disclosure rows.

* refactor(web): use .toggle-row iOS switch for Privacy preferences

Active/offline pills (the seg-control single-cell pattern that
NotificationsSection uses) read awkwardly for a flat preference list.
Switch the three telemetry toggles to .toggle-row — the same control
NewProjectPanel uses for "speaker notes" / "animations": label + hint
on the left, iOS-style sliding switch on the right, full-row click
target. The consent card's two-button seg-control stays as-is — there
the equal-weight pill pair is exactly what GDPR equal-prominence wants.

* feat(web): standalone first-run privacy consent banner

Replaces the Settings-dialog-as-onboarding hack with a dedicated
bottom-right banner card that mounts whenever the user has never made
a privacy decision (cfg.installationId === undefined). The banner is
prominent (anchored to the corner with a soft shadow) but
non-blocking, mirrors cookie-consent UX, and shares the project's
panel styling — same .modal-elevated background, --radius-lg corners,
--shadow-lg lift.

Wiring:

- App.tsx imports PrivacyConsentModal and renders it at the root,
  gated on installationId === undefined && !settingsOpen so it doesn't
  double up with the Privacy tab's own consent card when Settings is
  already showing.
- Share / Don't share both go through handleConfigPersist, so the
  resulting installationId + telemetry prefs land in localStorage and
  the daemon at the same time, reusing the existing autosave plumbing.
- The previous attempt that pinned the welcome SettingsDialog to the
  Privacy section is reverted; onboarding now stays focused on agent
  configuration, and the consent decision lives in its own surface.

* fix(web): keep privacy banner visible while Settings welcome modal is open

The banner gated itself on `!settingsOpen` to avoid double-rendering
with the Privacy tab's consent card. But the first-run path opens the
Settings welcome modal automatically when `onboardingCompleted=false`,
which fired immediately after bootstrap — so the banner flashed for a
moment and then vanished behind the modal backdrop.

Drop the `!settingsOpen` clause so the banner stays mounted whenever
the user has not yet made a privacy decision, and bump its z-index
above the modal backdrop (200 vs 100) so first-run users can actually
reach the consent buttons. The minor visual overlap with the Privacy
tab's own card is fine: clicking either copy resolves both surfaces.

* copy(privacy): soften consent button labels

Banner action buttons now read "Help improve Open Design" / "Not now"
(en, with hand translations in zh-CN / zh-TW and English placeholders
in the other 13 locales) instead of "Share usage data" / "Don't share".

The new wording aligns the affirmative action with the kicker copy
("Help us improve Open Design") and reads less alarming, while the
disclosure list above still names both data categories explicitly so
the consent stays informed under GDPR. The decline button stays as a
soft "Not now" rather than an aggressive "Don't share" so the reject
path doesn't read as hostile to the user.

No structural change — the two-cell seg-control still gives the buttons
identical visual weight, and the underlying side-effects are unchanged
(installationId is generated on Help / nulled on Not now, and the
telemetry prefs flip the same way).

* feat(telemetry): expand trace fields for evals & dataset construction

Each Langfuse trace now ships the full per-turn + per-install fact
sheet that the eval/dataset workflow needs, instead of only the bare
turn id + token count from before. Everything below is gated by
`prefs.metrics === true`; nothing here is content (those gates remain
separate).

Per-turn:
- model — first-class generation.model field, drives Langfuse cost
  lookup and model-grouping in the UI; also mirrored in trace.metadata
  and trace.tags so list-view filters work.
- reasoning — generation.modelParameters.{ reasoning } so the Model
  Parameters card lights up; mirrored in metadata.
- skillId / designSystemId — metadata + tags, so dataset slices can
  group by which skill/DS produced which output.

Per-process / build (constant within one daemon run, cached at start):
- appVersion / appChannel / packaged from app-version.ts
- nodeVersion (process.version), os (platform()), osRelease,
  arch (os.arch())
- clientType — desktop vs web, derived from a new X-OD-Client header
  the web layer sets in providers/daemon.ts (with a User-Agent sniff
  fallback for third-party callers).

Plumbing:
- startChatRun stashes model / reasoning / skillId / designSystemId
  on the run object alongside the existing userPrompt stash.
- POST /api/runs reads X-OD-Client and stores run.clientType.
- langfuse-bridge collects RuntimeInfo once per process and merges
  per-run client carrier; ReportContext gains optional `turn` +
  `runtime` blocks; existing fields stay backward compatible.

Spec gains a "Telemetry Fields Catalog" section enumerating every
field, its source, and the gate it lives under, so the eval team has a
single place to look up what's available without reading the trace
schema by example.

Tests:
- new langfuse-trace tests cover turn tags, runtime tags, generation
  model/modelParameters promotion, modelParameters omission when
  reasoning is unset, and metadata mirroring.
- langfuse-bridge gains an end-to-end "turn-level config" test that
  threads model/reasoning/skill/DS/clientType + appVersion through
  the bridge and asserts the Langfuse payload shape.
- existing tests adjusted to tolerate host-dependent os tag.

* copy(privacy): trim Share button to verb phrase only

"Help improve Open Design" overflowed the equal-width 2-cell
seg-control on the consent banner — the product name is already in
the kicker + headline above the buttons, so the button itself only
needs the verb phrase. Drop the product name from all locales:

- en: Help improve Open Design → Help improve
- zh-CN: 帮助改进 Open Design → 帮助改进
- zh-TW: 協助改進 Open Design → 協助改進

The decline button ("Not now" / "暂不" / "暫不") was already short, so
the two buttons now have comparable length and the equal-prominence
seg-control fits cleanly. Standalone Settings → Privacy panel uses
the same labels for consistency.

* fix(web): defer Settings welcome modal until privacy decision is made

Previously bootstrap raced two surfaces against each other on first
launch: the privacy consent banner (gated on installationId ===
undefined) and the Settings welcome modal (gated on
onboardingCompleted === false). The banner's higher z-index kept it
above the backdrop visually, but having two foreground surfaces at
once is still confusing UX.

Sequence them instead: bootstrap only opens the welcome modal when
the user has *already* resolved consent (installationId !== undefined).
Until then the banner owns the foreground alone. Once the user clicks
Help improve / Not now, the corresponding handler hands off to the
welcome modal if onboarding is still pending. End state matches what
it was before — just without the simultaneous-render flash.

* debug(privacy): log banner gate state to track sudden disappearance

Two console.log points to find which setCfg call (or stale bundle) is
flipping cfg.installationId from undefined to a value while the banner
is visible. To remove once the regression is reproduced.

* fix(privacy): keep installationId + telemetry out of localStorage

Daemon is now the single source of truth for the privacy decision.
Why this matters: the consent banner gates on
\`config.installationId === undefined\`, but loadConfig() merges
localStorage on top of the daemon's reply, so a stale uuid in
\`open-design:config\` (left over from a previous opt-in) was
re-hydrating the React state and immediately syncing back to the
daemon — defeating "Delete my data" and re-suppressing the banner
within milliseconds of every page load.

The deeper reason to fix it here, not just patch the gate: a privacy
identifier persisted in browser storage that the user can't see or
clear without DevTools is a compliance liability. Anything users can
revoke needs one canonical place to store it. Daemon \`app-config.json\`
already serves that role for everything else gated through
syncConfigToDaemon, so installationId + telemetry now ride that path
exclusively:

- saveConfig() strips both keys before writing localStorage.
- loadConfig() strips both keys when reading older stale payloads,
  so existing installs migrate transparently on next launch.
- syncConfigToDaemon() / mergeDaemonConfig still round-trip them, so
  the React state stays in sync with the daemon as before.

Net effect: clearing app-config.json (or hitting "Delete my data") now
fully resets the install identity, with no residual cohort key in
browser storage.

* feat(privacy): scrub secrets + PII from prompt/output before send

When prefs.content is on, daemon now runs the prompt and assistant
text through a regex scrubber (apps/daemon/src/redact.ts) before
posting to Langfuse. The scrubber is the simplest thing that gives
the user-facing copy a truthful claim — pure regex, zero new
dependencies, fully auditable in this Apache-2.0 repo (vs. pulling a
single-maintainer 5-month-old npm package into a core process).

Categories covered (each replaced with [REDACTED:<kind>]):

- Anthropic / OpenAI sk- keys (incl. proj/live/test/ant variants)
- Langfuse pk-lf- / sk-lf- (specific rule wins over generic sk-)
- GitHub gh[opsur]_ tokens
- AWS access key ids (AKIA + 16 uppercase)
- Google API keys (AIza + 35)
- Slack xox[abprs]- tokens
- Stripe live/test keys
- JWT header.payload.signature triples
- Bearer-header values (scheme word stays readable)
- Emails, IPv4, US-style phone numbers
- Credit cards — 13–19 digit runs that pass a Luhn check, so order ids
  and unix-nanos timestamps that fail Luhn pass through unchanged

Not covered, stated openly in spec + i18n: names, postal addresses,
business-secret semantics, raw 40-hex tokens (too high a false-positive
cost for artifact slugs). Those would require an ML layer.

Wired in:
- apps/daemon/src/redact.ts — exports redactSecrets() +
  redactSecretsWithCounts() helper for future audit-summary metadata.
- apps/daemon/src/langfuse-bridge.ts — runs both prompt and output
  through redactSecrets() before they reach the trace builder.
- 18 unit tests cover every pattern plus negative cases (Luhn-failing
  digit runs, out-of-range IPv4 octets, idempotence on re-redacted
  text, ordinary prose passthrough).
- i18n privacyContentHint on en + zh-CN + zh-TW (plus 14 locale
  placeholders) enumerates the categories so the consent disclosure
  matches the implementation — the GDPR informed-consent requirement.
- spec gains a Pre-send Redaction subsection with the regex shape
  table + intentional non-coverage list.

Drive-by: dropped the [privacy] debug logs that traced the now-fixed
bootstrap regression.

* fix(telemetry): make Langfuse reporting resilient

* feat(telemetry): nest Langfuse turn observations

* feat(telemetry): emit Langfuse tool spans

* fix(telemetry): report after finalized message writes

* fix(telemetry): honor persisted terminal status

* fix(web): let consent banner yield page clicks

* fix(telemetry): report current turn prompt only
2026-05-09 10:06:01 +08:00
f4eb1f1779 fix(web): hide the unsupported Save comment button on Pods selections (#993)
The board comment popover already short-circuits Save comment for
selectionKind === 'pod' by setting disabled=true, so the button shows
up greyed-out alongside Add note and Send to chat. That looks like a
disabled-because-of-input-state action even though the operation is
not supported on Pods at all, which is the same misleading "shown but
not real" pattern issue #792 asked us to clean up.

Drop the rendering entirely when selectionKind === 'pod' so the
footer collapses to Add note + Send to chat (which are the actual
Pods affordances) and the disabled prop only carries the draft-empty
constraint for non-Pods selections.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-09 09:23:55 +08:00
01851aa32a feat(design-systems): add Slack design system (#899)
* feat(design-systems): add Slack design system

* fix(i18n): cover slack design system in de/ru/fr fallback lists

The localized-content coverage test scans design-systems/*/DESIGN.md
and asserts every id appears in each locale's designSystems list.
Adding the slack design system without a locale fallback entry breaks
de/ru/fr CI.

---------

Co-authored-by: lefarcen <935902669@qq.com>
2026-05-09 09:02:27 +08:00
0d66643f33 Add Cisco and Webex design systems (#991)
* Add Cisco and Webex design systems

* Address review feedback for Cisco and Webex systems

---------

Co-authored-by: Rahul Jain <rajain5@cisco.com>
2026-05-09 09:02:19 +08:00
Paul SteanandGitHub 0b039777b9 fix/Bug#772-DesignFilesSortButton-DesignFilesTableNowSortableColumns (#804)
* Disclaimer: Changes made using OpenCode with Big Pickle, an AI code assistant.

1. Removed the ↑ button from DesignFilesPanel.tsx (was a "back" button that
    closed the preview pane — confusingly placed in the header as if it were for
    sorting).

2. Converted the file list to a single <table> with sortable columns:
    - Replaced the section-based grouping (Pages, Sketches, Scripts, Images,
    Other) with a flat, sortable table
    - Added column headers: Name, Kind, Modified — all clickable to toggle
    sort direction
    - Default sort is by modified time descending (same as previous behavior)
    - Sort indicator arrows (↑/↓) show the active sort column and direction
    - Live artifacts remain as a separate section above the table

3. Added i18n keys designFiles.colName, designFiles.colKind, designFiles.
    colModified to all 17 locale files and the type definitions.

4. Updated CSS with table layout styles (.df-table, .df-file-row, column
    width classes, sortable header styles).

    Files modified:
    - apps/web/src/components/DesignFilesPanel.tsx
    - apps/web/src/index.css
    - apps/web/src/i18n/types.ts
    - apps/web/src/i18n/locales/en.ts
    - apps/web/src/i18n/locales/*.ts (all 16 other locale files)

* Updated to preserve keyboard access to sorting

* Fixed keyboard to focus/activate/launch file from Design Files list. Single space bar will show preview, double spare bar will open the file as a tab

* Top pagination bar (above the table):
     - "Show" dropdown with options 15, 30 (default), 45, 60, All
     - Page range indicator (1–20 of 45)
     - Previous / Next buttons

     Bottom pagination bar (below the table):
     - Previous / Next buttons
     - "Go to page" dropdown listing all page numbers
     - Same page range indicator

     Implementation details:
     - All controls use native <select> and <button> elements — fully keyboard
     accessible (Tab, arrow keys, Enter/Space)
     - Page resets to 0 when page size changes
     - safePage clamps to valid bounds when file count changes (e.g. after delete)
     - "All" sets page size to total file count (effectively one page)
     - Prev/Next buttons show disabled state at boundaries with reduced opacity

* All 46 test files, all 385 tests pass. Here's what the regression test covers:
     ┌────────────────────┬──────────────────────────────────────────────────────┐
     │Test                │What it verifies                                      │
     ├────────────────────┼──────────────────────────────────────────────────────┤
     │default page size   │500 files → only 30 .df-file-row elements in DOM      │
     ├────────────────────┼──────────────────────────────────────────────────────┤
     │page size All       │changing per-page to "All" shows all 500 rows         │
     ├────────────────────┼──────────────────────────────────────────────────────┤
     │page size 60        │changing to 60 shows 60 rows                          │
     ├────────────────────┼──────────────────────────────────────────────────────┤
     │Next navigation     │clicking Next advances page and shows file-31 (sorted │
     │                    │by mtime desc)                                        │
     ├────────────────────┼──────────────────────────────────────────────────────┤
     │Prev/Next disabled  │Prev disabled on page 0, Next disabled on last page   │
     │states              │                                                      │
     ├────────────────────┼──────────────────────────────────────────────────────┤
     │jump to page        │bottom dropdown jumps to page 3 (shows file-91)       │
     ├────────────────────┼──────────────────────────────────────────────────────┤
     │page info text      │1–30 of 500 → after Next → 31–60 of 500               │
     ├────────────────────┼──────────────────────────────────────────────────────┤
     │render time         │renders 500 files in under 2s                         │
     └────────────────────┴──────────────────────────────────────────────────────┘

* Fixed i18n for DesignFiles, and Fixed DesignFilesPanel Test

* Fixed - P3 — .df-thead rule defined but never applied

* Fixed keyboard use for file navigation, focus and button usage

* Fix i18n for x of y in design files pagination

* Fixed SafePage clamping

* Fixed dupe file total count

* Fixed x of y i18n

* Fixed DeleteSelected i18n and missing from Test

* fix effective pagesize issue, and change duplicate file kind to a filesize

* Readded page/everything selection and i18n

* Fixed i18n issues

* Resolved indonesian i18n issue with cloudflare keys

* Fixed unrelated cloudflare i18n issues as requested in Pull Request by reviewer

* Fix e2e test: click filename button instead of row for preview

The DesignFilesPanel was refactored from <button> rows to a <tr> with
a nested <button> for the filename. The e2e test was still clicking
the <tr> which has no onClick handler, so the preview never appeared.

* Remove duplicate formatSize helper, reuse humanBytes instead
2026-05-09 09:01:57 +08:00
644a7daf2d fix(web): bump deploy modal footer top padding so primary action breathes (#992)
Issue #913: in the deploy modal, the primary action button sits flush
against the divider that separates the dialog body from the footer,
which reads as visually cramped because the form above already pushes
content right up to that border.

The shared .modal-foot rule uses 12px vertical padding which works for
shorter dialogs (sketch close, template save) but feels tight in the
deploy flow where the body ends in dense token / domain config rows.
Add a deploy-modal-scoped override that nudges the footer top padding
to 18px so the primary button has the same breathing room as the
content's column gap. Bottom padding stays at the shared 12px so the
overall modal height does not jump.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-09 08:59:07 +08:00
a7ac2a81b5 fix(web): scroll the active workspace tab into view when the strip overflows (#990)
Issue #775: when many files are open the tab strip scrolls horizontally
and the active tab can sit fully off-screen, so users lose track of
what they have selected. Design Files is already sticky-pinned at the
left edge from PR #842, but every other tab still vanished once the
overflow grew past the viewport.

Add a small effect keyed on activeTab that finds the .ws-tab.active
element inside the existing tabsBarRef and calls scrollIntoView with
{ block: 'nearest', inline: 'nearest' }. That makes opening a tab via
Cmd+P, clicking a chat file chip, or activating from the design panel
guarantee the tab is visible without yanking the user around when it
already was. The Design Files tab is excluded because the sticky CSS
already keeps it visible.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-09 08:58:53 +08:00
arijiiiitttt🍉andGitHub 5300585845 Fix Image template creations not automatically executing the selected prompt (#752) 2026-05-09 08:30:48 +08:00
84e1bb3b1a fix(web): confirm before closing a dirty sketch so unsaved strokes are not lost (#988)
Issue #139: clicking Close in the sketch editor immediately discarded
unsaved strokes with no confirmation, even though the toolbar already
tracked dirty state and surfaced the bullet indicator.

Wrap the existing onCancel in a window.confirm() guard, gated on the
dirty flag so a clean sketch still closes in one click. Same pattern
the codebase uses for conversation/design/file delete and the
media-provider Clear button.

i18n: new key 'sketch.closeConfirm' translated across all 17 locales.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-09 08:29:30 +08:00
72fd9a73a2 fix(web): keep chat auto-scroll glued to bottom across streaming chunks (#989)
Issue #983: when an assistant turn streamed in, the chat log stopped
auto-scrolling even though the user was sitting at the bottom of the
conversation. Root cause: the auto-scroll effect re-measured
`scrollHeight - scrollTop - clientHeight` AFTER the new chunk had
already grown the element. A single tool-use card or markdown render
adds 100+ px in one tick, so the post-content distance check (`< 80`)
skipped the scroll exactly when the user expected it most.

Switch the gate to the existing `scrolledFromBottom` state. That flag
is maintained by the user-driven scroll listener (only flipped by a
real scroll event, not by content growth), so it carries the user's
pre-content intent through to the effect. New content auto-scrolls
when the user was glued to the bottom; scrollback sessions still
preserve their position.

Existing chat-scroll-preservation tests still pass (6/6); the prior-
state behavior we test there is bottom-pinned vs absolute-restore on
tab switch, which this change does not affect.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-09 08:15:15 +08:00
34d781b364 fix(web): differentiate selected, hover, and focus states in the language switcher (#987)
Issue #628: hover and selected rows in Settings → Language used the same
--bg-subtle background, so the currently-selected language was hard to
distinguish from a row being hovered, and keyboard focus had no separate
treatment at all.

- Selected row gets --accent-tint background + accent text + medium
  weight so it reads as the active choice even when nothing is hovered.
- Selected + hover lifts to --accent-soft so the cursor still gives
  feedback over the active row.
- :focus-visible adds a 2px accent outline (inset) so keyboard users
  see exactly where they are while scrolling the list.

CSS-only change; the existing .active class wiring in the React component
already drives this.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-09 08:14:18 +08:00
83ddf7609c fix(web): correct srcdoc injection and deck bridge for JS strings con… (#938)
* fix(web): correct srcdoc injection and deck bridge for JS strings containing HTML tags

- Use indexOf for </head> (real tag precedes JS string occurrences)
- Use lastIndexOf for </body> (real tag follows JS string occurrences)
- Scope deck bridge slide selector to direct children of deck containers
  to avoid counting cloned overview thumbnails
- Update .slide-number data attributes and .progress-bar width from
  deck bridge's updateDeckChrome so page counter and progress track
  with deck navigation

* fix(web): move deck chrome sync into report() for all slide modes

.slide-number and .progress-bar updates were only in updateDeckChrome()
which is called exclusively from setActive(). Scroll decks go through
scrollGo() → report() without ever calling setActive(), so their page
counter and progress bar stayed frozen.

Move the sync logic into report() which is the convergence point for
all navigation paths (class-toggle, scroll, keyboard-dispatch).

* fix(web): use DOMParser for srcdoc injection instead of string matching

Replace all regex/indexOf/lastIndexOf based </head> and </body> matching
with DOM-based injection via domMutate helper. DOMParser correctly
separates raw-text element content from structural markup, so </head>/
</body> inside JavaScript strings no longer hijack injection points.

Also: scope deck bridge slide selector to direct children of deck
containers, and move .slide-number/.progress-bar sync into report()
so all navigation paths update page chrome.

* fix(web): add robust string fallback for srcdoc injection in non-browser environments

When DOMParser is unavailable (Node tests), fall back to structural
boundary matching: lastIndexOf('</head>', before <body) and
lastIndexOf('</body>', before </html>) to skip literal occurrences
inside script/style blocks. Annotate catch blocks.

---------

Co-authored-by: yangjingting <yangjingting@yxqiche.com>
2026-05-09 02:41:49 +08:00
Jie ZhuandGitHub 32c36f44a7 fix(i18n): rename live artifact tab label in zh-CN and zh-TW (#969)
* fix(i18n): rename live artifact tab label in zh-CN and zh-TW

Remove redundant '新建/新建' prefix from live artifact tab and title
labels to match other tab naming convention (e.g., '原型', '幻灯片').

- zh-CN: '新建实时制品' → '实时制品'
- zh-TW: '新建即時成品' → '即時成品'

* fix(i18n): revert titleLiveArtifact to original value

Only tab label should be renamed; title should keep '新建' prefix
to match other title labels (新建原型, 新建幻灯片, etc.).
2026-05-09 01:15:14 +08:00
Nicholas-XiongandGitHub f50c34fcd4 fix: ensure Settings close button is always clickable (#971)
- Increase .settings-chrome z-index from 3 to 10 to ensure it stays above all modal content
- Remove pointer-events: none from .settings-chrome container to avoid click interception issues
- .settings-autosave already has pointer-events: none, so it won't block clicks

The close button was becoming unclickable after PR #681 introduced the new
Settings layout with Orbit summaries. The issue was likely caused by either
z-index stacking or the pointer-events pattern not working reliably in all
scenarios.

Fixes #905
2026-05-09 00:16:26 +08:00
05461c64fd fix(connectors): show stable curated tool count in connector card badge (#748) (#767)
* fix(connectors): show stable curated tool count in connector card badge (#748)

The connector card's "N tools" badge in `apps/web/src/components/
EntryView.tsx` rendered `connector.tools.length` for both pre- and
post-Composio-hydration states, so the displayed count lurched
without explanation:

- Before configuring a Composio API key, GitHub showed "2 tools"
  (the static fallback catalog at `composio.ts:21-53`).
- After hydration, the daemon merged in the full Composio provider
  inventory (`composio.ts:725-778`, ~868 GitHub tools), and the
  same badge jumped to "868 tools".

Same field name, two different concepts — `connector.tools` is
"everything the provider ships" while the agent-callable subset
is `definition.allowedToolNames` (read-only auto-approval tools
that pass `isAgentPreviewListableTool()`).

Fix: surface `allowedToolNames` on the wire `ConnectorDetail` and
have the badge use that count instead. The detail drawer below
the card still iterates over `connector.tools` to enumerate the
full inventory — the count and the list are intentionally
different surfaces.

The badge now stays close to "tools the agent can actually
invoke" (≈2-30 for GitHub, depending on auto-allowed read tools)
instead of the raw provider inventory (~868). No 800x jump on
hydration.

Wire format change:
- packages/contracts/src/api/connectors.ts: add
  `allowedToolNames: string[]` to ConnectorDetail
- apps/daemon/src/connectors/catalog.ts: same field on the
  daemon-internal type, populated in `connectorDefinitionToDetail()`
  as a defensive copy of `definition.allowedToolNames`
- packages/contracts/src/examples.ts: extend the example fixture
- apps/web/src/components/EntryView.tsx: badge call sites switch
  to `connector.allowedToolNames?.length ?? connector.tools.length`
  (the `??` keeps the badge alive against any older daemon build
  that hasn't shipped the field yet)

Tests:
- 4 new daemon tests in connectors-service.test.ts pin the
  contract: getConnector() emits the field, the array is a
  defensive copy, and the #748 regression guard simulates the
  Composio post-hydration shape (tools.length=801,
  allowedToolNames.length=1) to prove the badge invariant
- web EntryView.test.ts fixtures updated to satisfy the new
  required field

Verified locally:
- daemon vitest: 925/925
- web vitest: 332/332
- daemon/web/contracts typecheck clean
- i18n-check passes
- Live `/api/connectors/discovery` returns the new field; pre-
  hydration GitHub/Notion/Google Drive badges all read "2 tools"
  (no regression vs before this change)

Fixes #748

* fix(connectors): split drawer badge vs inventory counts; fix daemon test typecheck (#767 review)

Two follow-ups for @mrcfps's review on PR #767.

1) **P1: Drawer empty-state regression.** The previous commit reused
the curated `toolCount` for both the header badge AND the inventory
section's loading gate / empty-state branch / section count. The
inventory section renders `connector.tools` directly, so a hydrated
connector with raw provider tools but an empty allowlist (e.g. a
write-only Composio surface) would render "no tools available" and
hide the actual inventory list — exactly the contradiction my own
PR description warned against.

Fix splits the two surfaces:
  - `badgeToolCount` (curated, via the new exported helper
    `getConnectorBadgeToolCount`) feeds the card and drawer header
    badge — the summary count, where the #748 stability matters.
  - `inventoryToolCount = connector.tools.length` (inline) drives the
    drawer's loading gate, section count, and empty-state branch —
    the surfaces describing the actual rendered list.

The card has no inventory section so it stays on the badge helper
unchanged.

2) **CI: daemon test typecheck failed.** The connectors-service test's
`provisionedTools[0].safety` index access tripped daemon
`tsconfig.tests.json`'s `noUncheckedIndexedAccess` strict setting,
even though my local `tsc -p tsconfig.json --noEmit` was clean —
that config is a separate compilation. Bind through a defined-checked
local before reading `.safety`, per @mrcfps's exact suggestion.

Tests:
  - 4 new web tests in `EntryView.test.ts` pin the
    `getConnectorBadgeToolCount` contract, including the explicit
    regression: a connector with `allowedToolNames=[]` and
    `tools.length=800` returns badge=0 but the inventory length
    stays at 800 — the drawer's empty-state branch must use the
    inventory count, never the badge count.
  - Existing daemon test fixed without losing assertion coverage.

Verified locally:
  - daemon vitest: 921/921
  - web vitest: 336/336 (was 332, +4)
  - daemon `tsc -p tsconfig.json` and `tsc -p tsconfig.tests.json` (the CI killer): both clean
  - web `tsc -b --noEmit` clean
  - i18n-check passes

Process learning baked into this PR: from now on I'll always run the
`tsconfig.tests.json` separately before pushing, since the workspace
typecheck script chains both and the second one is what CI fails on.

* fix(connectors): pin badge to curated catalog count, not the dynamic execution allowlist (#767 review v2)

@lefarcen and @mrcfps both flagged that the previous iteration of
this PR (commit 447a270) used `allowedToolNames` as the badge
source. That field isn't actually stable across Composio hydration:
`apps/daemon/src/connectors/composio.ts:758-761` extends it with
every provider-discovered tool whose classified safety is
read+auto, so a connector like GitHub goes from 2 → ≈52 on
hydration — better than 2 → 868, but still a 26x jump and still
the kind of unexplained number-lurch the issue is about. My
regression test only covered write-classified tools so this
read-path inflation slipped through.

Fix: introduce `curatedToolNames` as a separate field that is
locked to the static catalog and never extended by discovery.

- `packages/contracts/src/api/connectors.ts` and
  `apps/daemon/src/connectors/catalog.ts`: add
  `curatedToolNames: string[]` to `ConnectorDetail` (required on
  the wire) and as an optional field on
  `ConnectorCatalogDefinition` (defaults to allowedToolNames at
  serialize time).
- `apps/daemon/src/connectors/composio.ts`:
  `definitionFromToolkit()` now sets
  `curatedToolNames = [...staticDefinition.allowedToolNames]` —
  the catalog set, no extension. The runtime `allowedToolNames`
  keeps its dynamic auto-allow behavior so the execution gate is
  unchanged end-to-end.
- `apps/daemon/src/connectors/catalog.ts`:
  `connectorDefinitionToDetail()` populates
  `curatedToolNames: [...(definition.curatedToolNames ?? definition.allowedToolNames)]`,
  so non-Composio connectors (no discovery layer) trivially mirror
  the two fields.
- `apps/web/src/components/EntryView.tsx`:
  `getConnectorBadgeToolCount` becomes a 3-tier fallback —
  `curatedToolNames` first, then `allowedToolNames`, then
  `tools.length`. The order means a half-deployed daemon (allowed
  but not curated) still produces a more meaningful number than
  the raw inventory; only when both are missing do we fall back
  to the original buggy behavior.

Tests added:
- daemon `connectors-service.test.ts`: 3 new tests pin the
  contract — wire shape, hydration stability (catalog stays at 1
  while allowedToolNames grows to 51), and defensive copy.
- web `EntryView.test.ts`: 5 tests on `getConnectorBadgeToolCount`
  including the @lefarcen scenario explicitly: tools=800,
  allowedToolNames=52, curatedToolNames=2 → badge=2.

Verified locally:
- daemon vitest: 924/924 (was 921, +3)
- web vitest: 337/337 (was 332, +5 net)
- daemon `tsconfig.json` and `tsconfig.tests.json` (the CI
  killer): both clean
- web `tsc -b --noEmit` clean
- contracts typecheck clean
- i18n-check passes
- Live wire format: `/api/connectors/discovery` ships
  `curatedToolNames: ['github.github_search_repositories', 'github.github_get_issue']`
  for GitHub (length 2)

* fix: make ConnectorDetail.{allowed,curated}ToolNames optional in shape

After merging main, several existing test fixtures (EntryView,
SettingsDialog.orbit, ConnectorsBrowser) declared `ConnectorDetail`
inline without the new `allowedToolNames` / `curatedToolNames` keys
the PR introduced. Daemon-built payloads always populate both fields
(see `connectorDefinitionToDetail` and the Composio normalization
path), so making the wire shape `?`-optional is safe for runtime
consumers and avoids touching every fixture. Updates the daemon-side
mirror type to match and adds non-null assertions in the daemon tests
that read these fields directly.

---------

Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 23:42:52 +08:00
tenderpoohandGitHub 109722de3a feat(desktop): export artifacts directly to PDF (#532)
* feat(desktop): export artifacts directly to PDF

* fix(desktop): PDF 내보내기 기본 여백 제거
2026-05-08 23:42:12 +08:00
Muhammad AliandGitHub 0b757c2452 feat(skills): add clinical-case-report skill (#581)
* feat(skills): add clinical-case-report skill

Adds a new healthcare skill for generating structured medical case
presentations (SOAP format, conference, and ward rounds).

Files added:
- SKILL.md — od: frontmatter + full agent workflow instructions
- references/checklist.md — P0/P1/P2 medical accuracy validation
- references/case-formats.md — SOAP, conference, and rounds formats
- examples/example-stemi.html — inferior STEMI with cardiogenic shock

Fills the healthcare vertical gap in the current skill catalog.
Includes physiologically consistent vitals, labs, and an
evidence-based management plan using real clinical guidelines.

* fix: address review feedback from lefarcen and mrcfps

- Add prescribing safety gate (Step 5) — warns about missing allergy,
  renal, weight, and pregnancy context before drug recommendations
- Soften physiologic rules from 'must follow' to 'typical patterns' —
  acknowledges afebrile pneumonia, beta-blocker-blunted shock, etc.
- Preserve user-provided values even if atypical for the diagnosis
- Remove incorrect TIMI 0-7 score (UA/NSTEMI scale) from STEMI example;
  retain Killip Class III and Shock Index 1.27
- Fix troponin units: hs-troponin reported as 2400 ng/L (ref <40 ng/L)
  instead of conventional 2.4 ng/mL
- Add table accessibility: <caption> and scope='col' on vital signs
  and laboratory results tables
- Expand PHI checklist item to cover indirect identifiers (MRNs, dates,
  locations, rare conditions, occupation, verbatim stories)
- Disambiguate format selection guide ('ward round' maps to Brief Rounds,
  'formal rounds' maps to SOAP)
- Add example.html at skill root for /api/skills/:id/example resolver

* i18n: add clinical-case-report to DE/FR/RU skill fallback lists

* fix: soften checklist P0 vital signs rule to allow clinical variability

* fix: add medication safety checks block before antiplatelet section in examples

* fix: correct eGFR/age in safety block, add prescribing-safety P0 checklist items

* fix: correct age 67 to 58 in pregnancy line of safety block

* fix: defer norepinephrine dose to local protocol until weight confirmed

* fix: wire reference files into workflow; defer beta-blocker until shock resolved

* fix: close html code fence before Step 7 so checklist gate renders as prose

* fix: restrict oxygen to hypoxaemia only; generalise social history for de-identification

* fix: format-conditional P0 HPI gates; Killip III->IV for cardiogenic shock; smoking status consistent

* fix: make Step 2 and 'What you will produce' format-conditional for Brief Rounds

* fix: remove occupation detail from social history to comply with P0 de-identification rule

* fix: add 'ward rounds' plural to Brief Rounds format-selection table

* fix: gate Step 1 clarification on format; accept Killip+Shock Index as ACS risk scores
2026-05-08 22:36:37 +08:00
Nicholas-XiongandGitHub 727936ecb7 fix: remove redundant Connect GitHub placeholder from import menu (#964)
GitHub connection is already available through the Connectors tab.
The disabled 'Connect GitHub' item in the chat composer import menu
was a redundant placeholder that created confusion about duplicate
entry points.

This removes the placeholder to streamline the UI and direct users
to the primary GitHub connection flow via Connectors.

Fixes #777
2026-05-08 22:32:48 +08:00
soulmeandGitHub e3423c2b7b feat: add draggable file tab reordering (#936) 2026-05-08 22:21:19 +08:00
5d7568ba2c fix(web): confirm before clearing a saved Media provider API key (#875)
The Clear button on Settings → Media providers wiped the saved
apiKey + baseUrl + model in a single click with no recovery — a
fat-fingered click on the wrong row would silently delete a key
the user just pasted in.

Wrap the existing onClick in `window.confirm()` matching the same
pattern the codebase already uses for destructive actions
(conversation delete, design delete, FileWorkspace file delete).
The prompt is localized via a new `settings.mediaProviderClearConfirm`
key with `{name}` placeholders for the provider label, translated
across all 17 locales.

Updated the existing media-provider clear test to auto-accept the
prompt, plus added a sibling test asserting that dismissing the
prompt leaves the saved config intact.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 22:15:59 +08:00
f951ccb612 fix: keep examples filter counts consistent (#949)
* fix: keep examples filter counts consistent

* test: cover scoped examples scenario counts

* test: satisfy examples fixture typing

---------

Co-authored-by: leprincep35700 <leprincep35700@users.noreply.github.com>
2026-05-08 21:41:52 +08:00
ce5f20918c test: cover model option rendering (#948)
* test: cover model option rendering

* fix: strengthen model option regression coverage

---------

Co-authored-by: leprincep35700 <leprincep35700@users.noreply.github.com>
2026-05-08 21:38:13 +08:00
Priyanshu KayarkarandGitHub 9f6a918796 Fix: windows inspect overlay (#944)
* fix(web): improve the hint overlay behaviour

* feat(web): update inspect hint styling

* fix(web): add title and aria-label for closing hint box

* fix(web): remove the dead code from index.css

* fix(web): make default state of openhintbox  to true

* fix(web): allow pointer events to pass through hint overlay
2026-05-08 21:19:53 +08:00
Siri-RayandGitHub 208f09c60e fix: settle completed runs and clean up shutdown children (#924)
* fix: clean up completed and shutting down runs

* fix: bound daemon CLI shutdown

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* fix: harden daemon shutdown cleanup

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* fix: harden daemon shutdown cleanup

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)

* test: align acp abort fake with typed child
2026-05-08 21:05:22 +08:00
netteeandGitHub 32d820e4ee fix(daemon): typecheck leaf modules (#943)
* update drift

* fix(daemon): typecheck leaf modules

* fix(daemon): decode Qoder stdout buffers

Generated-By: looper 0.5.6 (runner=fixer, agent=opencode)
2026-05-08 20:01:25 +08:00
CaprikaandGitHub 4f647f56ba [codex] Optimize Composio connector previews (#907)
* Optimize Composio connector previews

* Fix partial connector tool preview hydration

* Cancel pending connector authorization on daemon

* Preserve Composio cached tool counts

* Avoid pending state after OAuth launch failure

* Preserve static tool count fallback

* Fix connector preview retry state

* Remove Composio auth config metrics

* Hydrate unknown connector tool previews

* Fix remaining connector review threads

* Stop failed connector preview spinner

* Hydrate only targeted agent connectors
2026-05-08 20:01:06 +08:00
1e8926271b Harden security scan findings and upgrade dependencies (#806)
* feat: add accent color control and launcher for Open Design

* fix: remove launcher binary from PR

* test: cover accent appearance edge cases

* Harden security scan findings and upgrade deps

* Address proxy security review

* Pin jsdom for web test stability

---------

Co-authored-by: ferasbusiness666 <ferasbusiness666@users.noreply.github.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 19:46:34 +08:00
d592f6087f feat(mcp): external MCP client with daemon-managed OAuth and 39 design-focused templates (#898)
* feat(mcp): add external MCP client with daemon-managed OAuth and 17 design-focused templates

Open Design now acts as an MCP CLIENT and surfaces tools from third-party
MCP servers to the underlying agent (Claude Code, Hermes, Kimi).

Daemon
- New mcp-config / mcp-oauth / mcp-tokens modules: persist server entries
  to .od/mcp-config.json, run the OAuth dance for HTTP/SSE servers
  end-to-end on the daemon (so cloud deployments work and tokens
  survive across turns), and inject Authorization: Bearer headers into
  the per-spawn .mcp.json the daemon writes for Claude Code (or the
  ACP mcpServers map for Hermes/Kimi).
- /api/mcp/servers and /api/mcp/oauth/{start,status,disconnect}
  endpoints, plus spawn-time wiring in agents that hands the configured
  servers to the active agent CLI.
- System-prompt directive for connected external MCPs so the model
  does not chase Claude Code's synthetic *_authenticate /
  *_complete_authentication tools when the Bearer is already pinned.

Web
- Settings -> External MCP servers panel with per-row OAuth Connect /
  Disconnect / Refresh affordances and per-row template hints.
- New "Add server" picker categorized into 7 groups
  (image-generation, image-editing, web-capture, ui-components,
  data-viz, publishing, utilities) with a search box, sticky close
  button, collapsible <details> sections (auto-expand on search),
  60vh capped scroll region, and a pinned Custom-server footer.
- ChatComposer /mcp slash and MCP picker button forward to the new
  Settings tab; AssistantMessage renders MCP tool calls inline;
  markdown autolinker handles bare http(s) URLs (incl. OAuth links)
  before italic markers so OAuth callback URLs do not get
  italic-fragmented mid-token.

Contracts
- packages/contracts/src/api/mcp.ts owns the wire shapes
  (McpServerConfig, McpTemplate with stable McpTemplateCategory
  enum, McpServersResponse, OAuth start/status/disconnect bodies, the
  postMessage payload from the OAuth callback).

Templates (17 built-in)
- image-generation: Higgsfield (OpenClaw, OAuth HTTP), Pollinations,
  Allyson (animated SVG), AWS Bedrock Image (uvx).
- image-editing: Imagician, ImageSorcery.
- web-capture: just-every screenshot-website-fast, ScreenshotOne.
- ui-components: 21st.dev Magic, shadcn/ui, FlyonUI.
- data-viz: AntV Chart, Mermaid.
- publishing: EdgeOne Pages.
- utilities: Filesystem, GitHub, Fetch.

Tests
- apps/daemon/tests/mcp-{config,oauth,tokens,spawn}.test.ts cover
  storage round-trip, OAuth helpers, token persistence, spawn-time
  wiring, every template's transport / command / args / env-field
  invariants, and the canonical category enum.
- apps/web/tests/runtime/markdown.test.tsx covers the new autolinker
  ordering rules.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(mcp): add 21 more design-focused templates and a `design-systems` category

Expands the built-in MCP picker from 17 to 38 templates so users can compose
the full Open Design craft loop (design-system intake → generate → edit →
audit → publish) without leaving the Settings dialog. Every install spec is
verified live against the upstream README; templates that needed Go binaries,
multi-step `init` ceremonies, or massive runtime stacks (PostgreSQL + Redis
+ Ollama) are intentionally deferred so picking a template still resolves to
a working server in one click.

New `design-systems` category between `web-capture` and `ui-components`
(reflects the upstream-of-components position in the workflow). Mirrored in
`McpTemplateCategory` on both contracts and daemon, and `CATEGORY_ORDER` on
the web side.

New templates by category:

- image-generation (+4): prompt-to-asset (icons / favicons / OG / logos with
  free-tier routing across Cloudflare AI / NVIDIA NIM / HF / Stable Horde),
  Nano Banana (hosted streamable HTTP, virtual try-on + product placement),
  Seedream (hosted streamable HTTP, ByteDance Seedream v3-v5 + SeedEdit),
  fal.ai (uvx, 600+ models incl. FLUX / Kling / Hunyuan / MusicGen).
- image-editing (+3): Photopea (34 layered-editor tools — closes the PSD
  gap), Topaz Labs (AI upscale / denoise / sharpen), Transloadit (86+ media
  pipeline robots).
- web-capture (+1): Pagecast (browser → demo GIF / MP4 with auto-zoom).
- design-systems (+4, NEW category): Figma-Context (Framelink, designs →
  code), Design Token Bridge (Tailwind ⇄ CSS ⇄ Figma ⇄ M3 / SwiftUI / W3C
  DTCG + WCAG contrast), Design System Extractor (Storybook scrape),
  Aesthetics Wiki (cottagecore / dark-academia / y2k / … moodboards).
- data-viz (+2): MCP Dashboards (45+ chart types + KPI dashboards),
  Excalidraw Architect (hand-drawn architecture diagrams).
- publishing (+6): PageDrop, PDFSpark, OGForge, QRMint, Slideshot
  (HTML → PDF / PPTX / PNG with 7 themes), Deckrun (Markdown → PDF / video,
  hosted free tier with no key required).
- utilities (+1): A11y axe-core (WCAG 2.0/2.1/2.2 + color-contrast + ARIA).

Tests cover every new template's wiring (command, args, env / header
required-vs-optional, secret flag), the category enum invariant, and
in-category declaration order for image-generation, design-systems and
publishing buckets where the order is what users see in the picker. 21 new
test cases pass; full mcp-config suite is green.

Templates intentionally deferred (documented in PR body): figma-use
(needs Figma desktop with --remote-debugging-port=9222), m-moire (multi-step
`memi suite init` + daemon ceremony), gemini-media-mcp + trident-mcp (Go
binaries — no npx / uvx path), Pixelle-MCP (full app with web UI + ComfyUI
backend), storybook-addon-mcp (lives inside user's Storybook, not standalone),
primitiv (multi-step init / build / serve), ReftrixMCP (PostgreSQL + Redis +
Ollama + DINOv2), narasimhaponnada/mermaid (overlap with peng-shawn).

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(mcp): add figma-use template (write designs from chat) under design-systems

figma-use is the natural counterpart to Figma-Context already in this PR:
where Framelink reads Figma designs into the model, figma-use writes back
into the canvas (90+ tools — create frames / text / components / variants,
render JSX into Figma, export PNG/SVG, query nodes via XPath, lint for
WCAG / auto-layout / hardcoded colors, analyze design systems).

Wired as an HTTP MCP template (`http://localhost:38451/mcp`) because
`figma-use mcp serve` only exposes HTTP — there's no stdio mode in the
upstream `serve.ts`. No API key. Two prerequisites the user owns are
spelled out in the description so picking the template still resolves to
a working server: (1) start Figma with `--remote-debugging-port=9222`
(or `figma-use daemon start --pipe` on Figma 126+), and (2) leave
`npx figma-use mcp serve` running in a terminal.

Inserted between `design-system-extractor` and `aesthetics-wiki` so the
design-systems category reads as a workflow: read existing design (Figma
Context) → translate tokens (Token Bridge) → extract from Storybook
(Extractor) → write back to Figma (figma-use) → break creative block
(Aesthetics Wiki).

Tests cover the new template's transport (`http`), endpoint URL, the
empty header-fields invariant (no auth required), and bump the
design-systems group order to include it.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(settings): i18n the External MCP / MCP server / Connectors sidebar entries and make the dialog header track the active section

The External MCP sidebar entry this PR introduces was hardcoded English
("External MCP / Add MCP tools (Higgsfield, GitHub…)"). Same for the
adjacent Connectors and MCP server entries. The dialog header was also
pinned to "Execution & model" copy, so opening Settings → External MCP
showed a header that lied about which section the user was on.

Adds six translation keys — `settings.connectorsTitle/Hint`,
`settings.mcpServerTitle/Hint`, `settings.externalMcpTitle/Hint` — and
translates them across all 17 locales (ar, de, en, es-ES, fa, fr, hu, id,
ja, ko, pl, pt-BR, ru, tr, uk, zh-CN, zh-TW).

`SettingsDialog` now derives the header title/subtitle from the active
section (11 sections total) instead of a single hardcoded pair, so each
section renders an honest header.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(e2e): pin level: 3 on dialog heading lookups for Pets and Connectors

CI's Validate workspace job (#1479) failed two Playwright cases with the
strict-mode violation:

  getByRole('dialog').getByRole('heading', { name: 'Pets' })
  resolved to 2 elements:
    1) <h2>Pets</h2>
    2) <h3>Pets</h3>

Same root cause as the unit-test fix already in this PR: the dynamic
dialog `<h2>` now echoes the section's own `<h3>` because the dialog
header tracks the active section. Disambiguate to `level: 3` so each
assertion still pins the section heading specifically (which is what
the test intends to verify).

Audit of the rest of e2e/ for `dialog.getByRole('heading', ...)` —
settings-api-protocol.test.ts looks for "OpenAI API" / "Anthropic API"
section h3s which never appear in the dialog `<h2>` (always
"Execution & model"), so those stay safe.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(mcp): bind OAuth refresh to the issuing client and skip stale tokens

Persist the OAuth client context (token endpoint, client_id, client_secret,
issuer, redirect_uri, resource) alongside the bearer token so refresh hits
the same client the refresh_token was bound to (RFC 6749 §6). The previous
refresh path re-ran beginAuth with a dummy OOB redirect URI, which kept
getOrRegisterClient from finding the original DCR client and made
providers reject the refresh on the next chat turn. Refreshes now reuse
the persisted endpoint/client pair directly.

Also stop injecting expired access tokens at spawn time when refresh is
unavailable or fails. Pinning a stale Bearer made every Claude MCP call
401 while the prompt still treated the server as connected; on that path
we now skip the entry and let the UI surface a reconnect.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:59:20 +08:00
2b5ea36f21 feat: add ib-pitch-book deck skill (Pitch Agent port) (#888)
* feat(skills): add ib-pitch-book deck skill (Pitch Agent port)

- Add investment-banking strategic-alternatives pitch book skill adapted from anthropics/financial-services Pitch Agent (Apache-2.0)

- Ship self-contained example.html (fictional NorthPeak / Hartfield case)

- Add references: compliance, attribution, conventions, P0/P1/P2 checklist

- Document in CHANGELOG [Unreleased]

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ib-pitch-book): align comps copy and DCF sensitivity base cell

- Trading comps: narrative now matches table (12.5× vs 12.4× median; explain via growth/mix)

- DCF: base-case sensitivity cell at 9.0% WACC × 2.5% g = $56.40, matching implied equity / share

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: register ib-pitch-book in i18n fallbacks; add demo disclaimer banner

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: address ib-pitch-book review feedback

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: prevent ib-pitch-book dense slide overflow

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:49:57 +08:00
1d1df52f3b feat(skills/live-artifact): add 7 example dashboards + contract demo (#716)
* feat(skills/live-artifact): add 7 example dashboards + contract demo

Seven self-contained HTML prototypes under skills/live-artifact/examples/,
each with a distinct visual identity and built-in interactivity for video
demos:

  stock-dashboard.html     - Bloomberg-style trading floor (dark)
  crypto-dashboard.html    - DeFi/web3 cyber terminal with on-chain ribbon
  crm-table-live.html      - multi-dim CRM with Grid/Kanban/Gallery/Calendar
                             view switcher (light productivity)
  monday-operator-live.html - editorial Monday-morning briefing (paper)
  competitor-radar-live.html - mission-control radar with rotating sweep
                             and RGB threat tiers
  baby-health-live.html    - soft pastel parental panel
  stock-portfolio-live/    - full live-artifact contract example: 102
                             escaped html_template_v1 bindings + 7
                             data-od-repeat blocks, ready to register
                             via 'tools live-artifacts create'

Each interactive HTML carries refresh-with-flash, view switching, AI
panel regeneration, clickable rows/cards that mutate state, and toast
notifications. Self-contained - only Google Fonts as external dep.

stock-portfolio-live/ demonstrates the daemon contract: template.html +
data.json + artifact.json + provenance.json. Refresh runners can rewrite
data.json without re-authoring the template.

* fix(skills/live-artifact): address PR #716 review feedback

- Unroll data-od-repeat blocks into indexed data.* bindings so renderHtmlTemplateV1 can interpolate them (it does not expand data-od-repeat or repeat-local aliases like {{t.label}}).
- Rename catalysts[].body to catalysts[].text to satisfy the bounded JSON validator's forbidden-key list (body is rejected case-insensitively); update template binding accordingly.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)

* fix(skills/live-artifact): make stock-portfolio provenance.json contract-compliant

- generatedBy: free-form string -> "agent" (LiveArtifactProvenanceGenerator enum)
- sources[].kind -> sources[].type with LiveArtifactProvenanceSourceType enum values
  (connector for brokerage/quotes connectors, derived for AI recommendation)
- Drop non-contract per-source `note` and top-level `summary`/`transformations`/
  `refreshContract`/`safetyNotes` fields; preserve their content under the
  contract-allowed `notes` field so the example survives schema validation.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)

* fix(skills/live-artifact): use strict ISO-8601 generatedAt in provenance

The daemon's `validateIsoDate` requires `Date.toISOString()` round-trip
equality, so timezone-offset notation like `2026-05-06T14:32:18-05:00`
fails validation even though it parses. Switch to the canonical UTC form
`2026-05-06T19:32:18.000Z` (same instant), which the validator accepts.

* feat(skills): surface examples/*.html as derived skill cards + Live filter

A skill that ships hand-crafted samples under examples/*.html (e.g.
live-artifact's stock dashboard, baby health monitor) now lights up one
gallery card per file instead of a single parent card whose preview can
only ever show one of them. The parent stays in the listing tagged
aggregatesExamples=true so findSkillById and Use this prompt still
resolve back to its SKILL.md body, but the Examples tab hides it so the
derived <parent>:<child> cards aren't shadowed by a duplicate preview.

Subfolder layouts (examples/<name>/template.html + data.json) are
deliberately skipped — their templates still hold {{data.x}}
placeholders that only the daemon-side renderer fills in, so showing
the raw template would render visible braces in the gallery. Ship the
baked output as examples/<name>.html alongside the folder to surface it.

Adds an examples.modeLive filter pill (translated across all 21 locales)
that selects skill.scenario === 'live', so refreshable / connector-backed
samples are easy to find without scrolling through every desktop
prototype. live-artifact's SKILL.md gains scenario: live so it (and
every derived card) lights up there.

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(web): parallelize entry-view bootstrap so each tab renders independently

Bootstrap used to wait on a single Promise.all behind a global
'Loading workspace…' placeholder, which made the slowest endpoint
(typically /api/agents on cold start, since it probes CLI versions)
gate every tab including the ones that don't need agents at all.

Splits the global bootstrapping flag into per-resource loading flags
(agentsLoading, skillsLoading, dsLoading, projectsLoading,
promptTemplatesLoading) plus a daemonConfigLoaded flag for the merged
daemon config. Each tab now blocks only on the data it actually needs:
Examples renders as soon as skills land, Design Systems on dsList,
Designs on projects+skills+designSystems, etc.

Auto-selecting the first available agent and the default design system
moves into dedicated effects gated on daemonConfigLoaded so they no
longer race ahead of the daemon-stored choice and overwrite it with a
freshly picked first-available pick.

EntryView swaps its single loading prop for skillsLoading,
designSystemsLoading, projectsLoading, promptTemplatesLoading so each
inner tab can pick the right gate without leaking the parent's coarse
state.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 17:38:29 +08:00
9ed4ea1263 feat(skill): add github-dashboard (#666)
* feat(skill): add github-dashboard

* docs(skill): add github-dashboard screenshot

* fix(skill): address github-dashboard review

* fix(i18n): cover github-dashboard skill in de/ru/fr fallback lists

The localized-content coverage test asserts every skills/<id>/SKILL.md
appears in each locale's skills list. Adding github-dashboard to the
EN-fallback id list keeps de/ru/fr CI green.

* fix(i18n): cover mission-control design system in de/ru/fr fallback lists

Same backfill as the one applied on PR #714: mission-control was added
in #858 without locale fallback entries, so the localized-content
coverage test breaks any open PR once GitHub merges current main into
its head ref.

---------

Co-authored-by: joey <joey@joeydeMacBook-Air.local>
Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 17:21:12 +08:00
SidandGitHub 8b0625aa6f fix(web): unbreak Create button on plain HTTP / LAN-IP deployments (#849) (#900)
`crypto.randomUUID()` is restricted to secure contexts (HTTPS or
`localhost`), so when Open Design is served over plain HTTP on a
LAN IP — the standard Docker / unRAID / NAS self-hosted setup,
e.g. `http://192.168.1.10:17573` — Chromium silently makes the
function undefined. Calls then throw
`TypeError: crypto.randomUUID is not a function`, which the
`try/catch` around `createProject()` swallows by returning `null`,
which the click handler reads as "no project, do nothing". The
Create button effectively becomes a silent no-op for every LAN-IP
user (issue #849, also reported as #394).

Centralize the call into a new `apps/web/src/utils/uuid.ts` helper
with a three-tier fallback per @lefarcen's review:

  1. `crypto.randomUUID()` — secure-context happy path, native and
     cryptographically random.
  2. `crypto.getRandomValues()` + RFC 4122 §4.4 byte layout — still
     available in non-secure contexts since the Web Crypto API is
     not gated by `isSecureContext`. Yields a real v4 UUID with
     crypto-quality entropy.
  3. `Math.random()` — last-resort polyfill for environments
     missing both, kept because the IDs we generate (project ids,
     message ids, client request ids) are scoped to a single
     user's local browser session — cryptographic uniqueness
     isn't required, just enough entropy to avoid collisions.

Replace all four `crypto.randomUUID()` callsites confirmed in
@lefarcen's audit:

- `apps/web/src/state/projects.ts:48` (createProject id)
- `apps/web/src/components/ProjectView.tsx:986` (user message id)
- `apps/web/src/components/ProjectView.tsx:1013` (assistant message id)
- `apps/web/src/components/ProjectView.tsx:1263` (daemon stream
  clientRequestId)

with calls to the new `randomUUID()` helper.

Tests: 6 new tests in `apps/web/tests/utils/uuid.test.ts` cover
each fallback tier, RFC 4122 v4 format validation (regex + explicit
version/variant nibble checks), the explicit "doesn't throw when
`crypto.randomUUID` is undefined" assertion that pins the #849
root cause, and a 1000-iteration uniqueness check on the
`getRandomValues` path.

Verified locally:
- web vitest: 522/522 (was 516, +6)
- web `tsc -b --noEmit` clean
- `tsx scripts/i18n-check.ts` passes
2026-05-08 16:50:59 +08:00
shangxinyu1andGitHub 8fee22d358 Fix stuck chat runs and unintended cancels (#896)
* Fix stuck chat runs and unintended cancels

* Harden chat run stall watchdog
2026-05-08 15:47:44 +08:00
e14b8092ea feat: add Orbit activity summaries (#681)
* feat: add Orbit activity summaries

* fix(orbit): make runs navigable while agent continues

* fix(web): widen minimum chat panel

* feat: support Orbit template selection

* fix(daemon): avoid bogus skill side-file preflight

* fix(web): collapse orbit artifact project cards

* fix(web): preserve orbit project card titles

* fix: improve Orbit run daily briefing

* fix: handle Orbit digest data failures

* fix: load Orbit templates and connector tools reliably

* fix: keep Orbit summary counts consistent

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: apply Orbit template skill context

* fix: cache and curate connector tools for Orbit

* fix: align Orbit defaults and connector discovery

* fix: simplify Orbit template settings

* fix: move connectors into settings

* fix: compact connector settings catalog

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: address Orbit PR feedback

Generated-By: looper 0.6.1 (runner=fixer, agent=opencode)

* fix: prevent connector action button from stretching into pill

The icon-only connect/disconnect buttons in the embedded connectors
catalog inherited min-width: 92px / 106px from the non-embedded pill
rules, overriding the 24px square sizing and causing the buttons to
overlap the card head text. Reset min-width to 0 in the embedded
icon-only rule so the compact square layout holds.

* fix(web): align live artifact file rows

* fix: clean up Orbit connector settings lifecycle

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix: address Orbit review regressions

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* feat(web): localize Orbit and connector settings

* feat(web): gate Orbit runs without connectors

* feat(web): refine connector settings UX

* feat(web): safeguard Composio key clearing

* fix(web): refresh Composio tool badges

* feat(web): show connector logos

* feat(daemon): localize Orbit prompt window

* fix(daemon): clarify blocked connector callback closes

* test(daemon): harden flaky async probes

* fix(web): align Indonesian connector locale keys

* test(web): align connector browser props

* fix(web): preserve explicit credential clears

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): time out Composio logo proxy fetches

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): localize Indonesian connector settings copy

Translate the new connector settings strings in the Indonesian locale and lock them with a regression test so this surface no longer silently falls back to English.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): preserve discovered connector tools

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): preserve onboarding autosave completion

Keep settings autosave from clearing onboarding completion after the close gesture, and expose the desktop main types from source so workspace validation can typecheck packaged imports without a prior desktop build.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): defer Composio catalog cache hydration

Load persisted Composio catalog data only after the runtime data directory is configured so startup cannot read another namespace's cache. Add a regression test that exercises the module-load singleton path.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): treat discovery completion independently

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): preserve latest settings draft on close

Use the latest persisted settings draft when the dialog closes so onboarding completion does not race a stale daemon sync and overwrite newer Orbit/template selections.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): avoid syncing draft Composio key on Orbit run

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): localize Orbit settings copy

Translate the new Indonesian Orbit and autosave strings so the settings UI no longer falls back to English and the locale regression stays covered.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): prefer fresh connector catalog state

Keep refetched connector status/auth data authoritative while retaining discovery-only tool metadata so the connectors UI stays consistent after refreshes.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): declare Indonesian locale fallback keys explicitly

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): inline Indonesian fallback strings for CI

Replace the Indonesian locale's per-key English lookups with explicit strings so workspace typecheck no longer depends on brittle build-mode resolution in CI.

Add a regression test that blocks those per-key English lookups from reappearing in the CI-sensitive fallback sections.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): restrict proxied connector logos to image MIME types

Reject non-image upstream logo responses so the daemon never serves third-party HTML from its localhost origin.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* test(e2e): align settings dialog regressions

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): decouple Orbit runs from media sync failures

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): keep SPA catch-all export-compatible

Disable dynamic catch-all params for the exported SPA shell so Next.js static builds can emit the root route again. Add a regression test covering the route config against the web export mode.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): preserve Orbit config and workspace routes

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): block SVG in connector logo proxy

Reject SVG and other unsafe proxied logo responses so third-party logo content cannot execute under the daemon origin, while keeping raster logo fetches working and making rejected responses non-cacheable.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): fall back to static catalog for empty cache

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): disable Orbit run before connector gate resolves

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(desktop): export shipped desktop types

Point the desktop ./main type export at the generated declaration so installed consumers resolve the published file set.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): restore persisted question form selections

Render historical submitted answers directly so reloaded question forms keep their locked selections visible.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): retry forced media sync autosave

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): keep Composio logo timeout through body read

Keep the Composio logo fetch timeout active until the response body is fully consumed so stalled body reads abort and clear the inflight cache entry. Add a regression test that proves a delayed body read times out and the next request can recover.\n\nGenerated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): refresh Orbit gate after connector auth

Re-check connector availability when the settings window regains focus so Orbit unlocks as soon as a connector finishes authenticating in the same settings session.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): keep connector detail tool lists intact

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): ignore malformed Orbit summaries

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(e2e): stabilize design-system multi-select flow

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): cap Composio logo cache growth

Bound the Composio logo cache with LRU eviction and expired-entry pruning so repeated untrusted logo requests cannot grow daemon memory without limit.

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(daemon): bound proxied Composio logo payloads

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): align autosave settings tests

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): remove stray CSS conflict marker

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fixer: address PR #681 follow-up items

Generated-By: looper 0.6.2 (runner=fixer, agent=opencode)

* fix(web): restore restart routes and connector flows

* fix(web): keep SPA export route static

* fix(web): stabilize chat scroll tests

---------

Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 14:27:46 +08:00
shangxinyu1andGitHub aec9428b08 Fix desktop preview and packaged app interactions (#879)
* Fix packaged deck navigation interactions

* Fix connector auth in packaged app and localized content coverage

* Fix Electron connector browser handoff contract
2026-05-08 14:26:10 +08:00
lefarcenandGitHub b9d30aa30e test(web): de-flake chat-scroll-preservation across tab switches (#886)
The earlier shape installed instance-level Object.defineProperty mocks
on the *remounted* chat-log only after `await switchTab('Chat')`. Inside
that act() the component schedules a rAF that writes scrollTop on the
new element; depending on whether jsdom's rAF polyfill flushed before
the await resolved, the write either landed on the still-default
prototype setter (lost) or the not-yet-installed instance setter (also
lost). The instance mock's closure-captured remountedTop then served
its initial 0 forever and the assertion failed nondeterministically
across CI runs without any product-code change.

Patch the geometry at HTMLElement.prototype level so any chat-log
React mounts later automatically reads/writes through a
test-controlled `geom` object. The component's restore rAF can fire
at any point and still write to the same place the assertion reads
from. Verified 8/8 clean local runs.
2026-05-08 14:16:12 +08:00
c2e8fc3b02 feat(design-systems): add Urdu Modern (Indus Script) system (#714)
* feat: add Urdu Modern design system

* fix: address review comments (font mismatch, contrast, and i18n fallback)

* fix: address all review comments for Urdu design system

* fix: resolve i18n crash, font URL mismatch, and markdown syntax error

* fix: remove font URL space and update Quick Start font token

* docs: fix quick start link syntax and icon source order

* fix(i18n): cover urdu design system in de/ru/fr locale dictionaries

The localized-content coverage test scans design-systems/*/DESIGN.md and
asserts every id appears in each locale's designSystems list, and every
`> Category:` value is a key in designSystemCategories. Adding the new
`urdu` design system without these entries breaks de/ru/fr CI.

Add urdu to the EN-fallback id list and translate the new
'Editorial / Personal / Publication' category for all three locales.

* fix(i18n): cover mission-control design system in de/ru/fr fallback lists

Mission Control was added in #858 but its design-system id was never
added to the locale fallback arrays, so the localized-content coverage
test breaks once main and any open PR share the same merge ref.

---------

Co-authored-by: unknown <muhammadanas0261@gmail.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-05-08 13:05:55 +08:00
Sohaib KamranandGitHub 47a014d377 Add BMW M design system (#579)
* Add BMW M design system

* Address BMW M design system review feedback

* Fix BMW M palette swatch parsing
2026-05-08 12:49:32 +08:00
661d11e60b fix(web): confirm before clearing the saved Composio API key (#877)
The Clear button on Settings → Connectors removed the daemon-stored
Composio key in a single click with no recovery — a stray click
wiped a credential the user had to fetch back from app.composio.dev.

Wrap the existing onClick in window.confirm() matching the same
pattern the codebase already uses for destructive actions
(conversation delete, design delete, FileWorkspace file delete,
and the Media providers Clear button shipped alongside this in
issue #737). The prompt copy stays in English to match the rest
of the Composio section, which is hardcoded English today.

Updated the existing 'clears a saved Composio key' test to
auto-accept the prompt, plus added a sibling test asserting that
dismissing the prompt leaves the daemon-stored key intact in the
saved payload.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 12:39:04 +08:00
0c383af332 add trading analysis dashboard template skill (live artifacts) (#824)
* add trading analysis dashboard template skill for live artifacts

Package the Wall-Street-style dashboard as a template-mode skill with a default example, checklist, and seed template, and register i18n fallback coverage for the new skill id.

* fix(skill): address P1 chart axis labels, units, and legends

lefarcen review:
- checklist required >=3 charts with axis labels, units, and legends, but
  template ships with 2 charts and neither had labels/units/legends.
- Adjusted checklist P0 to require >=2 charts (matches actual template) and
  kept the axis-label / unit / legend sub-gates.
- Annotated both Option Greeks and Cumulative PnL charts with x-axis tick
  labels, y-axis tick labels, axis titles with explicit units (Strike $,
  Sensitivity Δ/Γ, Session Time ET, Equity $ USD), and a legend row naming
  each plotted series. Charts now satisfy the P0 gate.

* fix(skill): make localStorage access safe inside sandboxed preview iframes

mrcfps Looper review:
Open Design renders HTML artifacts in sandboxed iframes that allow scripts
but not same-origin access. The template's top-level localStorage.getItem /
setItem calls could throw SecurityError before demo, theme, palette, and
chart handlers were registered, leaving the artifact static in the primary
preview path.

Wrap reads/writes in safeGetTheme / safeSetTheme helpers that swallow
SecurityError so the document can still apply the active theme on
documentElement and continue initializing interactive handlers when storage
is unavailable. Persistence becomes best-effort, interactivity becomes
guaranteed.

* fix(skill): align Option Greeks x-axis tick labels with strike circles

lefarcen review (a346e80 regression):
The axis ticks added in a346e80 placed 145/150/155/160/165 at x=40/120/200/300/380,
but the linked .strike-150/-155/-160 circles already sit at cx=200/300/380. With
the previous labels, hovering Option Chain row data-strike=150 would highlight
the chart point at x=200, where the axis read 155. The label-versus-data hover
link was visually inconsistent.

Shift the axis tick labels to 140/145/150/155/160 at x=40/120/200/300/380 so
strike 150/155/160 labels sit directly under their circles, restoring the
table↔chart hover-link semantics.

---------

Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local>
Co-authored-by: Tuola Ge <gexingli@refly.ai>
2026-05-08 12:08:43 +08:00
2df8b775ec feat(skills): add 32 zhangzara HTML deck templates (#704)
* feat(skills): add 32 zhangzara HTML deck templates

Vendored from upstream MIT-licensed
zarazhangrui/beautiful-html-templates — one Open Design skill per template
(name prefix `html-ppt-zhangzara-`) so each template surfaces as its own
entry in the Examples panel and renders its own preview.

Each skill ships:
- SKILL.md (frontmatter + workflow), description, triggers, and
  od.upstream pointing at the source folder
- example.html (the self-contained deck; daemon's preview route looks
  for <skillDir>/example.html)
- template.json (upstream metadata snapshot, with `slug` re-prefixed to
  `zhangzara-<base>` and a `source` URL)
- assets/deck-stage.js / assets/styles.css for the 8 templates that
  ship a runtime; HTML refs rewritten so the daemon's iframe URL
  rewriter resolves them through /api/skills/<id>/assets/

scripts/guard.ts allowlist updated with the `html-ppt-zhangzara-` prefix
so the vendored upstream JS runtimes pass the residual-JS check.

* fix(skills, i18n): address PR #704 review feedback

- Add the 32 new html-ppt-zhangzara-* skill ids to the de/ru/fr
  SKILL_IDS_WITH_EN_FALLBACK arrays so the localized-content
  coverage e2e test passes. The vendored upstream templates are
  English-only; falling back to the upstream English description
  is the right semantic for this batch.
- Also add the pre-existing social-media-dashboard skill and
  totality-festival design system to the same fallback arrays
  (introduced in #678 without i18n coverage). Tagged with TODOs
  so localized copy can land in a follow-up.
- Ship the upstream MIT LICENSE file in each
  skills/html-ppt-zhangzara-*/ folder so the copyright/permission
  notice travels with the vendored copy, as MIT requires for
  redistributing substantial portions. Update each SKILL.md's
  Source section to reference the bundled LICENSE.
- For the 8 runtime-backed templates (creative-mode,
  editorial-tri-tone, neo-grid-bold, peoples-platform,
  pin-and-paper, pink-script, soft-editorial, stencil-tablet),
  expand the workflow's clone step to instruct the agent to copy
  the assets/ folder alongside example.html — the skill HTML
  references assets/deck-stage.js (and assets/styles.css for
  pin-and-paper) as project-local paths, so cloning the HTML
  alone produces an artifact whose runtime 404s.

Verified locally:
- pnpm guard passes.
- pnpm --filter @open-design/web typecheck passes.
- pnpm --filter @open-design/web test passes (309/309).
- pnpm --filter @open-design/e2e test passes (6/6 active,
  including localized-content coverage for de/ru/fr).

* fix(i18n): drop duplicate totality-festival fallback after merge with main

Main already added 'totality-festival' to the design-system EN-fallback
lists; the TODO entry from this branch became a duplicate after merge.

* fix(skills, guard): address PR #704 follow-up review

- Pin Chart.js CDN to 4.4.7 in coral and cartesian example.html so
  vendored decks no longer track the latest jsDelivr major.
- Narrow scripts/guard.ts zhangzara allowlist to a regex that only
  permits skills/html-ppt-zhangzara-*/assets/deck-stage.js, restoring
  the TypeScript-first guard for any other JS under those skill dirs.
- Reconcile slide_count and 'Slides in demo' with actual <section
  class="slide"> counts: broadside 20 -> 16, monochrome 18 -> 16,
  neo-grid-bold 13 -> 12.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(daemon): keep resolveDataDir return path stable, canonicalize at compare site

The realpathSync wrapper inside resolveDataDir was rewriting every
/var/... result to /private/var/... on macOS, which broke 11 hermetic
assertions in tests/resolve-data-dir.test.ts (absolute paths, relative
paths, and \$HOME / \${HOME} / ~ expansions whose mkdtempSync roots live
under /var/folders/...). It also changed the public OD_DATA_DIR
resolution contract for any downstream caller that compared against the
expanded user-supplied path.

Restore resolveDataDir to return the expanded resolved path unchanged,
and introduce RUNTIME_DATA_DIR_CANONICAL — a one-shot realpath of
RUNTIME_DATA_DIR — used only at the narrow folder-import comparison
site that needs to match against a user-supplied realpath() result. The
import-path symlink protection from #624 still works (a /var-rooted
data dir now compares against its /private/var canonical form), while
resolveDataDir keeps its stable, user-shaped contract.

Verified locally: pnpm --filter @open-design/daemon test (1083/1083),
including all 12 resolve-data-dir.test.ts cases.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 12:02:59 +08:00
netteeandGitHub 8930b9650c feat: Add a toggle to reveal media provider API keys (#867) 2026-05-08 11:46:21 +08:00
Priyanshu KayarkarandGitHub c55d058024 fix(web): differentiate recent and your designs sorting (#845)
* fix(web): differentiate recent and your designs sorting

* fix(web): remove the immediate return statement from sorting logic

* fix(web): add sorting your design tab by creation time

* fix(web): update card timestamps

* fix(web): align sort keys and timestamps across designs tab views
2026-05-08 11:34:53 +08:00
655d561f38 fix(web): show explicit error/retry state when example preview HTML fails to load (#863)
* fix(web): show explicit error/retry state when example preview HTML fails to load

Reporter (#860) saw the example preview modal stuck with the toolbar
buttons greyed out and only restarting the app got back to a usable
state. Lefarcen confirmed the diagnosis: when /api/skills/:id/example
fails, fetchSkillExample returns null, the modal stays at preview.loading
forever, and the share menu's disabled={!activeHtml} guard sits in the
disabled position with no recovery path.

Three changes:

1. fetchSkillExample now returns a discriminated { html } | { error }
   instead of collapsing every failure into null, so callers can tell a
   real fetch failure from a normal load.

2. PreviewView gains an optional error field. When set, PreviewModal
   renders a stacked title/body/Retry affordance instead of the
   indefinite "Loading…" placeholder. Retry re-fires onView so the
   parent can re-run its fetch.

3. ExamplesTab tracks per-skill errors alongside per-skill html, clears
   the in-flight value before each fetch, and wires onView from the
   modal into loadPreview so the Retry button actually retries.

i18n: three new keys (preview.errorTitle, preview.errorBody,
preview.retry), translated across all 17 locales. The locales-aligned
test stays green.

CSS: .ds-modal-error stacks the new content vertically inside the
existing .ds-modal-empty positioning, no other modals affected.

* fix(web): stabilize preview onView and guard parallel preview fetches

Codex caught a real bug in the round-1 fix: the inline
onView={() => loadPreview(...)} prop was recreated on every parent
render, and PreviewModal's mount effect re-fires onView whenever its
identity changes. A persistent fetch failure would update state,
recreate the prop, re-fire the effect, re-run loadPreview, and burn
through the error UI in a flash instead of waiting for a Retry click.

Pin a stable onPreviewView via a useRef-backed callback so the modal
sees a single identity for the lifetime of the panel; loadPreview is
reached through the ref, so its closure refresh on state updates no
longer leaks into the modal's effect dependencies.

While in this surface, also add lefarcen's race guard: a synchronous
inFlightRef Set so two parallel loadPreview calls (e.g. card hover
firing while the modal opens) cannot both pass the cache check before
either setState lands. The first caller adds the id pre-await; the
second sees it and exits early. try/finally clears the entry on both
success and failure paths.

Adds tests/components/preview-modal-error-state.test.tsx covering:
- error UI renders when view.error is set,
- Retry click calls onView with the active view id,
- re-rendering with the same onView identity does not re-fire the
  modal's mount effect (pins the no-auto-retry contract).

* fix(web): close Retry over the active skill id, not the modal-internal view id

mrcfps caught a real regression in round 2: PreviewModal calls
onView(activeId) where activeId is the modal-local view id ('preview'
in this component). The previous round forwarded that argument
straight into loadPreview, so the mount effect and Retry button hit
/api/skills/preview/example instead of /api/skills/{skill-id}/example.
The new error state could not actually recover.

Mirror the active skill id into a ref alongside loadPreviewRef and
have onPreviewView ignore the modal-forwarded argument, fetching the
selected skill via the ref instead. The callback identity stays
stable, so the no-auto-retry contract from round 2 still holds.

Adds tests/components/examples-tab-retry.test.tsx that mounts the
real ExamplesTab, mocks fetchSkillExample to reject, opens the
preview, clicks Retry, and asserts the second call hits the same
skill id (and explicitly never gets called with 'preview').

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-08 11:16:14 +08:00
kamiandGitHub 2eae7da24b feat: support Cloudflare Pages custom domains (#851)
* Support Cloudflare Pages custom domains without hiding pages.dev fallback

Keep the default Pages preview as the first public link while optional owned-zone binding provisions DNS and Pages custom-domain state in parallel.

Constraint: Cloudflare deploys must use the existing direct-upload API path with no Wrangler dependency.

Constraint: pages.dev must stay visible even while custom-domain verification is pending.

Rejected: Vercel custom-domain support | outside requested Cloudflare-only scope.

Rejected: overwriting arbitrary CNAME records | risks taking over user-managed DNS.

Confidence: high

Scope-risk: moderate

Directive: Do not expose providerMetadata through public deploy contracts; keep custom-domain DNS ownership checks conservative.

Tested: pnpm --dir apps/daemon exec vitest run -c vitest.config.ts tests/deploy.test.ts tests/deploy-routes.test.ts

Tested: pnpm --filter @open-design/contracts build && pnpm --filter @open-design/contracts typecheck && pnpm --filter @open-design/contracts test

Tested: pnpm --filter @open-design/web typecheck && pnpm --filter @open-design/web test -- providers/registry.test.ts components/FileViewer.test.tsx i18n/locales.test.ts

Tested: pnpm i18n:check && pnpm guard && pnpm typecheck

Tested: pnpm --filter @open-design/daemon build && pnpm --filter @open-design/web build && git diff --check

Not-tested: real Cloudflare account/token/domain smoke test

* Preserve Cloudflare fallback correctness under large accounts and races

Constraint: Cloudflare Pages keeps pages.dev as the primary usable fallback while custom domains remain optional typed metadata.
Rejected: Treating custom-domain DNS or binding failure as a top-level deployment failure | pages.dev can still be ready and usable.
Confidence: high
Scope-risk: moderate
Directive: Keep custom-domain finality tied to Cloudflare Pages API active status plus URL reachability; do not expose providerMetadata.
Tested: pnpm --dir apps/daemon exec vitest run -c vitest.config.ts tests/deploy.test.ts tests/deploy-routes.test.ts; pnpm --filter @open-design/web test -- components/FileViewer.test.tsx i18n/locales.test.ts providers/registry.test.ts; pnpm --filter @open-design/daemon typecheck; pnpm --filter @open-design/web typecheck; pnpm i18n:check; git diff --check; pnpm guard; pnpm typecheck; pnpm --filter @open-design/daemon build; pnpm --filter @open-design/web build
Not-tested: Real Cloudflare token/account/zone smoke test.

* Keep impeccable design notes local

Constraint: .impeccable.md is local assistant/design context and should not be part of the PR diff.
Rejected: Keeping the file tracked while adding it to .gitignore | tracked files are not ignored by Git.
Confidence: high
Scope-risk: narrow
Directive: Keep .impeccable.md untracked and ignored; do not rely on it for required project documentation.
Tested: git check-ignore -v .impeccable.md; git diff --check
Not-tested: Full workspace tests not rerun for ignore-only metadata change.
2026-05-08 11:11:22 +08:00