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>
* 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.
OpenCode Desktop ships two binaries on PATH after install:
- `opencode` — GUI launcher; double-clicking it opens the
desktop app, never reads stdin.
- `opencode-cli` — headless CLI; speaks the
`run --format json --dangerously-skip-permissions -`
protocol the daemon expects.
The OpenCode AGENT_DEF set `bin: 'opencode'`, so
`resolveAgentExecutable` resolved the GUI launcher for every user
with the desktop install. The daemon happily spawned that binary,
but the GUI launcher discards stdin and never emits the JSON event
stream — so the agent silently produced no output for the entire
conversation. Issue #814 tracks the user-visible symptom.
Fix: declare `bin: 'opencode-cli'` and `fallbackBins: ['opencode']`
on the OpenCode adapter. `resolveAgentExecutable` walks `bin`
first, then each fallback in order, so this gives us:
- Desktop install (both binaries on PATH) → resolves
`opencode-cli` (the CLI), GUI is ignored. The #814 fix path.
- Legacy CLI-only install (only `opencode` on PATH, no
`-cli` suffix) → falls through to `opencode`, which is the
actual CLI in that install shape, so the daemon keeps working
unchanged.
- Neither installed → falls through to null, same as before.
Same mechanism Claude Code uses to fall back to `openclaude`
(issue #235), so this is well-trodden ground in the codebase.
Tests:
- One declarative test asserts `opencode.bin === 'opencode-cli'`
and `opencode.fallbackBins.includes('opencode')` — runs on every
platform including Windows, catches anyone who accidentally
flips `bin` back to the GUI name.
- Two filesystem-backed tests pin the resolution priority: when
both binaries are on PATH (the desktop case) `opencode-cli`
wins; when only the legacy `opencode` is on PATH the fallback
takes over.
- Existing comment guarding "most agents have a single binary"
updated to remove `opencode` from the example list.
Verified locally:
- daemon vitest: 993/993
- daemon `tsc -p tsconfig.json` and `tsc -p tsconfig.tests.json`: both clean
- i18n-check passes
Reported by @giuliastro, diagnosed by @lefarcen who also asked for
the `opencode-cli --version` output in the issue thread — happy to
narrow this further if their reply turns up subcommand differences
between the two binaries.
Fixes#814
* 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
* 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.
* 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
* 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>
* 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.
* 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
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>
- Add QUICKSTART.zh-TW.md (Traditional Chinese) based on the latest
QUICKSTART.zh-CN.md, with regional terminology for zh-TW locale.
- Update the language nav in all 6 existing QUICKSTART translations
to include the 繁體中文 entry.
- Fix CONTRIBUTING.zh-CN.md: point QUICKSTART references to the
Chinese translation (QUICKSTART.zh-CN.md) instead of the English
original, closing the follow-up promised in PR #578.
- 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
* feat: add contributor card bot workflow
Adds a production workflow that triggers on merged pull requests and opened issues, checks out the standalone contributor bot, and runs it with the Open Design bot GitHub App credentials. The main repository only owns the workflow trigger; card rendering and Vaunt contribution lookup remain isolated in open-design-bot-sandbox.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: run contributor bot on pull_request_target
Use pull_request_target for closed merged PRs so the workflow receives repository secrets when external fork PRs are merged. The job only checks out the trusted contributor-bot repository, not contributor PR code, preserving the intended security model while allowing cards to post for fork contributors.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
* 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
* 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)
* 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
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>
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>
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>
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>
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>
* 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.
* docs: fix - update prompts path from web to daemon in README files
Update all references from apps/web/src/prompts/ to apps/daemon/src/prompts/
across all README language files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: fix - update prompts path in specs and fix French README parity
P2 — Current specs:
- specs/current/critique-theater.md
- specs/current/critique-theater-plan.md
Update prompts path from apps/web/src/prompts/ to apps/daemon/src/prompts/
to reflect daemon ownership of prompt composition.
P3 — Historical spec:
- specs/2026-04-29-live-artifacts/spec.md
Update to current path (historical snapshots kept accurate to latest codebase).
P3 — Language parity:
- README.fr.md
Fix missing prompt references in Anti-AI-slop and References sections
to match other language variants (6 refs total).
Migration context: Prompts moved from web to daemon as an ownership/boundary
change (daemon composes and injects prompts at agent spawn time), not a
mechanical rename. Web app no longer owns prompt composition logic.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: fix - update git command path in critique-theater-plan.md
Fix missed git add command example at line 1585.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: fix - update prompt test paths from web/tests to daemon/tests
Update test file paths to match daemon ownership of prompts.
Tests for apps/daemon/src/prompts/* should live in apps/daemon/tests/prompts/.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* docs: add skills contributing guide
External skill PRs are coming in faster than we can write per-PR
acceptance feedback, and the existing skill section in CONTRIBUTING.md
gave contributors the merge bar without showing the dev loop or the
patterns we routinely close on. This adds a dedicated guide that
contributors land on before opening a PR.
- New docs/skills-contributing.md (the how-to): quick start, anatomy,
local dev loop, merge bar checklist, PR description template, and
the eight rejection patterns we've actually used recently.
- CONTRIBUTING.md "Adding a new Skill" shrinks from 73 lines to ~20
and points at the new guide. Skill section was the longest in the
file; trimming it keeps the four i18n variants easier to maintain.
- skills/README.md is new — first thing a contributor sees when they
open the skills/ folder. Routes them to the contributor guide and
the protocol spec.
- docs/skills-protocol.md gets a cross-link at the top so readers who
land on the protocol can find the contributor flow.
Discovery is the point: any path a contributor takes (CONTRIBUTING.md,
skills/ folder, protocol spec) now routes to the same single guide.
* docs(skills-contributing): expand modes to 7, fix broken checklist link
Both flagged by review on #1035:
- The mode enumeration listed 4 modes (prototype | deck | template |
design-system) but apps/daemon/src/skills.ts:24 actually defines 7
(adds image | video | audio), with shipped media skills under
skills/{image-poster,video-shortform,audio-jingle}. Updates every
enumeration in skills-contributing.md (frontmatter cheat sheet, PR
template, running-locally instructions, IS-list) and skills/README.md.
- The merge-bar checklist pointed at skills/dating-web/references/
checklist.md as an example, but that path doesn't exist on this
branch — dating-web ships only SKILL.md + example.html. Repointed
at skills/web-prototype/references/checklist.md, which is the
closest prototype-mode skill that actually ships a checklist.
Adds a "Media skills (image / video / audio)" line to the references
section pointing at the three shipped media skills as imitate-able
starting points.
* docs(skills-contributing): address review — i18n bar, external skills, daemon refresh
Three findings from review on #1035:
P1 — i18n merge-bar mismatch (line 172 of original)
e2e/tests/localized-content.test.ts:144 enforces skills.toEqual(skillIds)
for every locale, so a non-featured skill PR following the previous
guidance ("no edits to apps/web/src/i18n/") would fail CI on the
`skills display copy` assertion.
Fix:
- "Single self-contained folder" item now explicitly carves out the
*_SKILL_IDS_WITH_EN_FALLBACK line as a required outside edit.
- New "i18n coverage (every skill, not just featured)" subsection
directs contributors to add their id to all three fallback arrays
(DE / FR / RU) — bare id, no TODO comment per existing convention.
- "Featured skills" subsection now describes replacing the fallback
with full localized copy, instead of being the only path that
touches i18n.
- PR template Validation list adds the fallback-arrays step as a
required checkbox for every skill PR.
P2 — daemon does not auto-watch skills/ (line 139 of original)
apps/daemon/src/skills.ts:2 explicitly states "No watching in this MVP
— re-scans on every [/api/skills call]". Previous wording about chokidar
was aspirational, not current behavior.
Fix: replaced with "refresh the picker — the daemon re-scans skills/ on
every /api/skills request" + restart escape hatch for parse failures.
P2 — missing alternative for vendor workflows (line 60 of original)
Previous "No" list pointed contributors at heavier daemon/feature paths
for vendor-specific workflows, ignoring that skills-protocol.md §3
supports user-global skills via ~/.claude/skills/. Concrete cases like
payment-provider and regional-marketplace skills (which we've been
closing as out-of-scope) actually fit the external-bundle path.
Fix: added a "Third option: ship as an external skill bundle" paragraph
before the discussions CTA, linking to the protocol's discovery section.
* 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>
When the connector callback page is reached via direct navigation rather
than the daemon's popup-driven flow (linked from a different tab,
opened in a new window by the OAuth provider, etc.), there is no
window.opener and any window.close() call is silently rejected by the
browser. The previous handler called close() anyway and only flipped the
manual-close hint after a 250ms timer that was gated on
visibilityState === 'visible'; if the visibility check ever read
'hidden' (the popup losing focus during the close attempt is enough),
the hint never updated and the button felt completely dead.
Detect the no-opener case up front. If there is no live opener, the
click skips the no-op close call and shows the manual-close hint
immediately so the user sees instant feedback. When there is an opener,
schedule the hint update unconditionally after the close attempt: if
close actually worked the page is unloading and the timer never runs,
otherwise the hint reliably surfaces. Also extracted the live-opener
check into a tiny helper so the auto-close path uses the same guard.
Co-authored-by: Nagendhra <nagendhra405@gmail.com>
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.
* 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