Commit Graph
241 Commits
Author SHA1 Message Date
617fb043fe feat(settings): add fetch models button for BYOK providers (#1034)
* feat(settings): add fetch models button for BYOK providers

* fix(settings): exclude Ollama from fetch models, add manual-entry hint

* fix(provider-models): classify non-JSON upstream errors by HTTP status

* fix(i18n): drop redundant English overrides from non-English locales

* fix(provider-models): allow ollama through allowlist, return unsupported_protocol

---------

Co-authored-by: haolin122 <hl6593@nyu.edu>
2026-05-09 22:28:03 +08:00
e2713cb0f4 refactor(web): flush sidebar tabs to edge with internal scrolling (#1038)
Remove borders, padding, and overflow from the sidebar container so the
NewProjectPanel tab component fills the full sidebar width. Move scroll
responsibility into the tab body so only the content panel scrolls, not
the entire sidebar. Tab buttons sit flush at the top edge.

Co-authored-by: 克隆(宗可龙) <zongkelong@soyoung.com>
Co-authored-by: mrcfps <mrc@powerformer.com>
2026-05-09 22:23:33 +08:00
Justin GaoandGitHub 49b55761d3 feat(daemon+web): add install/uninstall for skills & design systems (#1003)
* feat(daemon+web): add install/uninstall for skills & design systems (#497 Phase 2)

Phase 2 of the Library Settings feature. Adds the ability to install
skills and design systems from GitHub repos or local paths, and
uninstall user-installed items. Built-in items remain read-only.

Daemon:
- Multi-directory scanning: built-in + ~/.open-design/{skills,design-systems}
- Install from GitHub (git clone --depth 1) or local path (symlink)
- Uninstall removes cloned dirs or symlinks under user dir only
- 4 new API routes: POST install + DELETE for skills and design systems
- Reuse isBlocked() from linked-dirs.ts for path safety

Web:
- Install form with GitHub/Local tab switch in Library settings
- Source badge ("Built-in" / "Installed") on cards
- Uninstall button (trash icon) on installed items only
- Native-language i18n for all 17 locales

Contracts:
- Add source field to SkillSummary and DesignSystemSummary
- Add InstallInput, InstallSkillResponse, InstallDesignSystemResponse, UninstallResponse types

* fix(daemon): follow symlinks in scanners and respect OD_DATA_DIR for user libraries

Two fixes from PR review feedback:

1. Scanners (skills.ts, design-systems.ts) used Dirent.isDirectory() which
   skips symlinks on macOS/Linux, so locally-installed items never appeared
   in API responses. Now also accepts isSymbolicLink() entries.

2. User library directories were hardcoded under os.homedir(), ignoring
   OD_DATA_DIR. This broke test isolation and packaged runs. Now derived
   from RUNTIME_DATA_DIR so they respect the data dir override.
2026-05-09 22:19:48 +08:00
FinnandGitHub d30cf18941 feat(skills): add release notes one pager + supporting docs (#873)
* add release notes one pager skills and supporting templates

* Update SKILL.md and template.html for release notes: refined upgrade note and enhanced responsive design elements in the template.

* Update SKILL.md to clarify topnav CTA label and href destination requirements, and enhance instructions for handling missing details in release notes.

* Refactor buttons to links in release notes templates and checklist: updated button elements to anchor tags for improved navigation and added explicit checklist items for handling missing sections in release notes.

* Update topnav link labels in SKILL.md and template.html for release notes: changed placeholder links to specific destinations (#added, #fixed, #upgrade-note) for improved navigation clarity.

* Enhance SKILL.md by adding section ID requirements for release notes structure: ensure each section (Added, Fixed, Breaking changes, Known issues, Upgrade note) includes a corresponding `id` attribute for improved navigation and consistency.

* Update example.html for release notes: refine title, update logo text, and modify section summaries to indicate no entries or actions required.

* Add consistent spacing for lists in template.html and update checklist.md to require matching id anchors for release-note sections

* Update SKILL.md, example.html, checklist.md, and layouts.md for improved clarity and structure in release notes

* fix: p2 anchor + p2 dead links

* fix: add id to section block

* fix: replace placeholder URLs with real destinations in SKILL.md, checklist.md, and layouts.md

* fix: update release notes guidelines to omit CTAs without real destinations and ensure no placeholder URLs remain

* fix: clarify CTA handling in release notes and remove placeholder links from template

* fix: add guidelines for using Layout 7 in release notes to remove header CTA row

* fix: add 'release-notes-one-pager' to localized content IDs for French, Russian, and German

* fix: remove 'release-notes-one-pager' from localized content IDs for French, Russian, and German
2026-05-09 22:10:36 +08:00
Nicholas-XiongandGitHub 74c9158410 fix: increase top padding in modal footer for better visual balance (#957)
* fix: increase top padding in modal footer for better visual balance

Fixes #913

The primary action button in modal footers (e.g., Deploy to Cloudflare Pages)
was sitting too close to the top border, making the footer area feel cramped.

Changes:
- Increased top padding from 12px to 16px in .modal-foot
- Kept bottom padding at 12px to maintain existing bottom spacing
- Left and right padding unchanged (uses --modal-padding variable)

This provides better visual balance and makes the primary action button
feel less cramped against the divider line above it.

* fix: add fallback value for --modal-padding variable

The --modal-padding variable is only defined under .modal-settings,
but .modal-foot is also used by generic modals (FileViewer, SketchEditor).
Without a fallback, the padding declaration is invalid for those modals.

This adds a fallback value of 22px to ensure the footer padding works
for all modal types, not just settings modals.
2026-05-09 22:07:54 +08:00
Jie ZhuandGitHub 84f4b03cb9 fix: prevent project type selector arrows from overlapping tabs (#1091)
* fix: prevent project type selector arrows from overlapping tabs

* fix: apply padding to parent container for arrow safe zone
2026-05-09 21:59:24 +08:00
Jie ZhuandGitHub 68dc78030a fix(web): prevent creating new conversation when current is empty (#1086)
* fix(web): prevent creating new conversation when current is empty

* fix(web): use ref to track loaded conversation for race-free empty check

* fix(web): eagerly update ref on new conversation to prevent rapid-click duplicates
2026-05-09 21:55:51 +08:00
039cc3d674 fix(settings): add install onboarding links for unavailable local CLIs (#985)
* fix(settings): add install onboarding links for unavailable local CLIs

* fix(settings): rename Claude config dir label to config directory

---------

Co-authored-by: mrcfps <mrc@powerformer.com>
2026-05-09 21:49:44 +08:00
SidandGitHub 5db578123e fix(web): dispatch Examples preview on od.preview.type (#897) (#1001)
* fix(web): dispatch Examples preview on od.preview.type (#897)

The Examples gallery unconditionally fetched `/api/skills/:id/example`,
and the daemon endpoint only resolves HTML files (`example.html`,
`assets/template.html`, `assets/index.html`, `examples/*.html`). Skills
that declare `od.preview.type: image` (`hatch-pet`) or
`od.preview.type: markdown` (`dcf-valuation`, `last30days`,
`x-research`) ship no such HTML — the fetch returns 404 and the modal
landed on the misleading "Couldn't load this example. The example HTML
failed to fetch." copy.

Dispatch on `previewType` at the data layer (`fetchSkillExample`) and
at the render layer (`PreviewModal`):

- `fetchSkillExample(id, previewType)` short-circuits any non-`html`
  value to `{ unavailable: true, kind }` without firing a network call.
- `PreviewView` grows an optional `unavailable: { kind }` shape; the
  modal renders a calm "no shipped preview" placeholder distinct from
  loading and error states. The Share menu disables (no HTML to export).
- `ExamplesTab` tracks `previewUnavailable` per skill alongside the
  existing `previews` / `previewErrors` maps; the card placeholder
  swaps to "open to learn more" copy so users don't hover waiting for
  a render that won't come.
- New `preview.unavailableTitle` / `preview.unavailableBody` and
  `examples.unavailablePlaceholder` / `examples.shareUnavailable` keys
  shipped across all 17 locales. Body copy uses the raw preview kind
  (`{kind}` placeholder) so future kinds slot in without a copy change.

Tests: registry-level coverage that the dispatch never hits the network
for non-html types; PreviewModal-level coverage that the unavailable
affordance is mutually exclusive with loading/error and disables the
Share menu; ExamplesTab-level coverage that the gallery renders the
unavailable state for image/markdown skills and routes html skills
through the existing fetch path. Updated the existing `#860` retry
regression test for the new two-arg signature.

* fix(web): use neutral noun for preview.unavailableBody copy (#1001 review)

P3 from lefarcen: `'a {kind} document'` reads awkwardly when `{kind}`
is `image` ("a image document") and the article disagreement
undermined the PR body's claim that future kinds slot in without
copy changes. Drop the article and replace `document` with a more
neutral noun (`output` / `resultat` / `产物` / `出力` / etc.) so
every kind reads naturally:

- `produces {kind} output` (English)
- `produit un résultat {kind}` (French)
- `生成 {kind} 产物` (Simplified Chinese)
- … and 14 more

`{kind}` placeholder stays literal in every locale; surrounding
vocabulary for skill / prompt / chat preserved per existing
file conventions.
2026-05-09 21:32:45 +08:00
CIoudherdandGitHub 724d071c01 feat: add design file rename support (#894)
* feat(contracts): add project file rename contract

* feat(daemon): add safe project file rename API

* feat(web): support renaming design files

* fix(daemon): handle case-only file renames

* fix(web): prevent rename collisions with pending sketches

* fix(daemon): preserve source names during rename

* test(daemon): cover rename symlink escapes

* fix(daemon): avoid clobbering rename targets

* test(web): align rename tests after rebase

* test(web): align rename tests with latest main
2026-05-09 21:24:36 +08:00
meshackmandGitHub 461a312002 fix: fix link handling in example preview iframe sandbox (#701)
* fix: fix link handling in example preview iframe sandbox

* fix: added missing single quote

* fix(srcdoc): security/correctness issues
- Use location.hash = href for hash-link navigation so hashchange
  events, CSS :target, history/back and named anchors work correctly
- Always pass 'noopener,noreferrer' to window.open() for _blank links
- Guard e.target with instanceof Element before calling .closest() to
  avoid TypeError on text node click targets

* fix(srcdoc): security/correctness issues
- Added a protocol allowlist
- Re-navigation to same hash
- Consistency with rest of shim IIFE using var instead of let and const

* fix(srcdoc): scroll to top on bare hash links (#)

Intercept href="#" clicks and scroll to top of page in the iframe sandbox to mimic native browser behaviour.

* test(web): update PreviewModal sandbox test assertions to include popup flags
2026-05-09 21:24:25 +08:00
0264ad6150 fix(web): scroll Settings content back to top on section change (#997)
Switching to a different settings section (e.g. clicking About after
scrolling Library) kept the previous scrollTop on the right-hand
content panel, so the new section's heading often landed below the
fold and the panel read as half-loaded. The complaint in #634 was
specifically about reaching About from a sidebar position near the
bottom and finding the About content not fully visible.

Add a ref on .settings-content and a useEffect that resets scrollTop
to 0 whenever activeSection changes. The sidebar's own scroll position
is left alone, so users who scrolled to reach a tab still see that tab
in view after clicking it.

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-09 21:22:05 +08:00
Yuhao ChenandGitHub 2e0cf52764 fix(web): localize MCP server settings panel for Chinese UI (#760)
- Add 31 typed i18n keys in types.ts for MCP settings strings
- Add English values in en.ts (source of truth)
- Add zh-CN and zh-TW translations
- Move MCP_CLIENTS from global const into IntegrationsSection
  so buildMethod/buildInstruction/deeplinkLabel can use t()
- Wire all JSX strings in IntegrationsSection through useI18n
- Change deeplinkLabel from string to () => string to support
  dynamic translation

Command/config snippets and product names remain untranslated
per maintainer guidance.

Fixes #745
2026-05-09 19:43:40 +08:00
zztdanandGitHub fe879036fb fix(web): restore media config from daemon on startup (#687)
* fix(web): restore media config from daemon on startup

* fix(media): preserve stored keys on settings save

* fix(web): harden daemon media restore flow

* fix(web): unify media provider empty-state rules

* fix(desktop): retry loading discovered web url

* fix(web): preserve local media providers on partial daemon reload

* fix(web): preserve media providers on daemon reload

* fix(web): skip media migration for masked-only local state

* fix(web): preserve daemon media state across reloads
2026-05-09 19:31:08 +08:00
Tom HuangandGitHub 643d0cf637 feat: add scheduled routines for unattended agent runs (#1033)
* feat: add scheduled routines for unattended agent runs

Generalizes Orbit's single hard-coded daily-digest scheduler into
user-defined routines: each one fires on a schedule (hourly / daily /
weekdays / weekly with IANA timezone) and starts a fresh agent
conversation, either inside an existing project or in a new project
minted on the spot.

Backend:
- New RoutineService with timezone-aware nextRunAt computed via
  Intl.DateTimeFormat (no new dependency); two-pass tzWallToUtc so
  DST transitions stay correct. Each fire chains rescheduleOne in
  finally() to keep the cadence alive.
- routines + routine_runs SQLite tables; schedule_json is the
  authoritative form, with legacy schedule_kind/value kept populated.
- /api/routines CRUD + /api/routines/:id/run + /api/routines/:id/runs.
- Run handler resolves agent (routine override -> app config -> first
  available), creates project (or reuses configured one) and a fresh
  conversation per fire, then dispatches into startChatRun.

UI (Settings -> Routines):
- Pill-chip schedule kind picker, time + timezone fields, weekday
  picker for Weekly. Live preview line ("Runs daily at 9:00 AM GMT+8").
- Routine list with inline status pill, next/last meta, expandable run
  history; each history row links into the project the run wrote to
  via the existing router primitive.

* fix(daemon): swallow trailing finally rejection for inflight cleanup

Without a terminal `.catch`, the promise returned by `promise.finally(...)`
mirrors the original rejection and produces an unhandled rejection — fatal
in modern Node — when the run handler rejects before producing a start
handle. Callers still see the rejection on the returned `promise`.

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

* fix(daemon): handle DST spring-forward gap in tzWallToUtc

The two-pass conversion picked the pre-gap candidate when the requested
wall time fell inside a spring-forward gap (e.g. 02:30 in
America/New_York on 2026-03-08), so the resulting instant rendered back
as 01:30 local and a 02:30 routine fired an hour early on the
transition day. Routines are local wall-clock schedules, so firing
before the requested time breaks the contract.

Now we round-trip both candidates through partsInTimezone, return the
one whose wall-clock matches the request, and on a gap day where
neither matches return the later candidate so the routine fires at the
first valid post-gap instant on the same day.

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

* fix(daemon): preserve both wall-time candidates on DST fall-back day

On a fall-back day, the requested wall time inside the repeated hour
(e.g. 01:30 America/New_York) maps to two distinct UTC instants. The
previous tzWallToUtc collapsed them to the first (pre-transition) one,
so a daemon that woke between the two instants would skip the second
01:30 entirely and fire a day late once per fall-back. Replace it with
tzWallToUtcCandidates (returns all valid instants, ascending) plus a
gap-only fallback for spring-forward, and have nextWallTimeMatching
walk both ambiguous candidates before advancing to the next day. Adds
fixtures for the repeated-hour case so the intended behavior stays
locked in.

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

* fix(web): make routine timezone picker IANA-complete and DST-truthful

The timezone dropdown was a hardcoded subset, but the backend validator
accepts any IANA zone — so users could not pick zones like
`America/Phoenix` or `Africa/Johannesburg` unless they happened to be
local. And `gmtLabel()` always derived the offset from `new Date()`,
which drifted seasonally for DST-observing zones (a New York routine
created in winter rendered `GMT-5` while it would actually fire on
`GMT-4` after DST started).

Source the picker from `Intl.supportedValuesOf('timeZone')` (with a
curated fallback for older runtimes) and anchor the GMT label to the
routine's next fire time. When the next fire time is unknown (e.g.
the live preview while the form is open) and in the dropdown itself,
fall back to the IANA city, which is stable year-round.

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

* fix(web): always include UTC in routine timezone picker

`Intl.supportedValuesOf('timeZone')` returns only canonical region
names on current runtimes (Node 24, recent browsers) and omits `UTC`,
so the previous picker dropped the most common non-local zone unless
the runtime itself was already UTC. The backend validator and the
contract examples still accept `UTC`, so a user on a non-UTC machine
could not create a documented UTC routine from Settings.

Prepend `UTC` inside `listSupportedTimezones()` when the runtime list
omits it, so the picker stays aligned with the supported schedule
surface.

Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)
2026-05-09 19:30:22 +08:00
Paul SteanandGitHub f3535cdd9f feat(web): scroll question forms to top of viewport instead of pinning to bottom (#1044)
* feat(web): scroll question forms to top of viewport instead of pinning to bottom

* fix: reset scroll state on composer send after form has claimed control

* fix: use getBoundingClientRect for form scroll position; guard early-return on streaming

* fix: smooth scroll for all chat-scroll operations; polyfill scrollTo for jsdom tests

* fix: revert streaming bottom-pin to instant to avoid scroll event thrash

* fix: revert initial-load bottom-pin to instant to avoid scroll event thrash
2026-05-09 19:29:54 +08:00
343c1080e9 feat(design-systems): add hud, loom, trading-terminal with locale coverage (#1069)
* feat(design-systems): add hud, loom, trading-terminal with locale coverage

- Add hud, loom, trading-terminal to DE_DESIGN_SYSTEM_IDS_WITH_EN_FALLBACK
- Add loom and trading-terminal to FR_DESIGN_SYSTEM_SUMMARIES (FR locale)
- Add loom and trading-terminal to RU_DESIGN_SYSTEM_SUMMARIES (RU locale)
- Add DESIGN.md for hud, loom, trading-terminal design systems

* fix: restructure to canonical 9-section schema, fix WCAG claims, fix locale ordering

* fix: match canonical 9-section schema, correct WCAG guidance

* fix(trading-terminal): add missing --space-* tokens to :root block

* fix: align Loom spec — font labels (Inter not monospace), light-mode FR/RU summaries

---------

Co-authored-by: Ayush Mahadik <ayushmahadik@gmail.com>
2026-05-09 19:28:19 +08:00
79957890d2 feat(skills): consolidate hyperframes video template updates (#1079)
Bundle four pending template skills and retag eight related skills to video/hyperframes so the categorization and i18n fallback coverage can be reviewed and merged in one pass.

Co-authored-by: Tuola Ge <gexingli@refly.ai>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 18:09:30 +08:00
lefarcenandGitHub ba44de4193 fix(web): keep entry footer pills compact (#1045) 2026-05-09 18:08:47 +08:00
c0062e856a add swiss-creative-mode-template skill (#1068)
Introduce a new Swiss editorial template skill with interactive multi-scene HTML seed files and add DE/FR/RU fallback coverage so localized-content validation stays green.

Co-authored-by: tuolaji <tuola@tuolajideMacBook-Air.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 17:01:48 +08:00
8518292605 add editorial-burgundy-principles-template template skill (#1065)
Add a burgundy editorial live-artifact template with a headline metric slide, studio keyword cloud, and eight-principles card grid. Include DE/FR/RU fallback ids so localized-content coverage stays green.

Co-authored-by: Tuola Ge <gexingli@refly.ai>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 17:01:03 +08:00
dd439c29bf add swiss-user-research-video-template template skill (#1054)
Introduce a Swiss editorial user-research live-artifact template with self-contained HTML seed/example and checklist, and register i18n fallback ids for DE/FR/RU so localized coverage stays green.

Co-authored-by: Tuola Ge <gexingli@refly.ai>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 17:00:00 +08:00
ecddcd4fbd add after-hours-editorial-template template skill (#1053)
Add a new dark editorial HyperFrames template skill with seed/example/checklist assets and register the skill id in DE/FR/RU i18n fallback lists so localized-content validation stays green.

Co-authored-by: Tuola Ge <gexingli@refly.ai>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 16:59:27 +08:00
Marc ChanandGitHub 223d35f073 fix: improve Orbit and packaged data-dir startup errors (#1067) 2026-05-09 16:47:01 +08:00
Siri-RayandGitHub 671b9006f6 Fix desktop prompt template close hitbox (#1056)
* Fix desktop prompt template close hitbox

* Fix prompt template modal viewport spacing

Generated-By: looper 0.6.0 (runner=fixer, agent=codex)
2026-05-09 16:33:37 +08:00
EliandGitHub 1bf7836471 feat(web): redesign top bar — lift Share/Present, zoom dropdown, focus toggle (#1048)
* feat(web): redesign top bar — lift Share/Present, add zoom dropdown, move focus toggle

- AppChromeHeader: add #app-chrome-file-actions portal anchor so file viewers
  can render their primary actions (Present/Share) up in the project chrome
  instead of cramming a second toolbar row.
- HtmlFileViewer / LiveArtifactViewer: portal Present + Share into the top
  bar via createPortal; Share gets a real chrome-action-primary button.
- HtmlFileViewer: replace the 100% reset button with a zoom dropdown
  (50/75/100/125/150/200) with click-outside + Esc handling.
- HtmlFileViewer: move Preview/Source tabs next to Reload (left side, view
  modes); move Tweaks to the right cluster next to Inspect/Edit.
- HtmlFileViewer: showPresent no longer requires deck — any HTML artifact
  with loaded source can be presented (prototype/slide/regular HTML).
- LiveArtifactViewer: add Present (in-tab/fullscreen/new-tab) with iframe
  ref + previewBodyRef wrapper; in-tab present hides chrome and overlays an
  exit button (Esc also exits).
- ChatPane: add chevron-left collapse icon in chat header (onCollapse prop)
  so users can hide the chat from where it lives.
- FileWorkspace: focus toggle is now icon-only and only renders when chat is
  collapsed, sitting on the LEFT of the workspace tabs row as a chevron-right
  expand button — direction matches where chat re-emerges from.
- index.css: add chrome-action-primary/secondary, zoom-menu, present-exit-btn,
  app-chrome-file-actions styling, plus a narrow-width media query that
  collapses secondary action labels.

* fix(web): tests — fall back to inline render when chrome portal anchor missing

The Share/Present primary actions render via createPortal into
#app-chrome-file-actions, which only exists when AppChromeHeader has
mounted. Vitest renders FileViewer / LiveArtifactViewer in isolation,
so the portal anchor was absent and the buttons disappeared from the
test DOM, breaking 7 share-menu tests.

- HtmlFileViewer / LiveArtifactViewer: when chromeActionsHost is null,
  render the present/share JSX inline instead of returning null. UX is
  identical in production (host is always present); tests now find the
  buttons without needing a portal-aware harness.
- FileWorkspace tests: rewrite the "focus toggle in tab bar" assertions
  to reflect the new design — the toggle lives in ChatPane while the
  chat is open, and FileWorkspace only renders an icon-only expand
  button on the LEFT of the tab bar once chat is collapsed.
2026-05-09 15:26:22 +08:00
f5564c93a7 i18n: add full Thai translation (th) (#1018)
* i18n: add full Thai translation (th-TH)

* i18n: fix placeholders, update tests and complete documentation for Thai (th)

* i18n: fix placeholders, update tests and complete documentation for Thai locale

* chore: revert unrelated docker deployment changes (fix scope drift)

---------

Co-authored-by: ryu <ryu@example.com>
2026-05-09 15:19:47 +08:00
CaprikaandGitHub b020f1e39a fix opencode todowrite footer state (#1046) 2026-05-09 15:08:19 +08:00
lefarcenandGitHub 6f74ac304d fix(web): expand design file row click target (#1039) 2026-05-09 14:46:09 +08:00
lefarcenandGitHub 286b6cdf8d fix(web): make privacy consent choices explicit (#1031) 2026-05-09 14:37:43 +08:00
CaprikaandGitHub 8feb2e586c fix(connectors): preserve OAuth state and advertised tool counts (#1036)
* fix(connectors): preserve oauth state and advertised counts

* test(connectors): type fixture for advertised count

* docs(connectors): align tool count badge contract

* docs(connectors): clarify curated tool names role
2026-05-09 13:54:35 +08:00
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