* fix(web): keep connector errors out of cards
* fix(web): route panel-alert open-details through openConnectorDetails
The new "open details" action on the panel-alert row called
`setDetailConnectorId(alert.connectorId)` directly, bypassing the
`toolPreviewFailedIds` clear that `openConnectorDetails()` does on
every other entry point.
Concrete regression: user opens a connector's drawer from the card,
the tool-preview hydration fetch fails so `toolPreviewFailedIds[id]`
gets stamped with the current `toolPreviewRetryToken`, user closes
the drawer, and later the same connector throws a connect error and
shows up as a panel alert. Clicking the alert's open-details button
re-sets `detailConnectorId` but never clears the failed-fetch token,
so the hydration effect at the previous bail-out check
(`toolPreviewFailedIds[detailConnector.id] === toolPreviewRetryToken`)
returns immediately and the drawer never retries loading tools.
Routing the click through `openConnectorDetails(alert.connectorId)`
restores the same clear-before-set behavior the card and search
entry points already had, so the reopened drawer always gets a fresh
hydration attempt.
* test(web): cover connector panel alert tool retry
* fix(web): clarify connector panel alert status text
* fix(web): clear stale connector cancel alerts
---------
Co-authored-by: lefarcen <935902669@qq.com>
* Refresh French UI locale coverage
* Address French locale review feedback
* Sync French locale with latest main
* Update French locale after latest main
* ci: add PR-author and stale-issue inactivity workflows
Adds two queue-management automations:
- pr-author-inactivity: reminds PR authors after 72h of inactivity
following human reviewer/maintainer feedback (issue comments,
non-approval reviews, or inline review comments) and closes after
120h. Author response is detected via issue comments, inline review
replies, or commit/force-push events. Bot-authored reviews are
intentionally excluded so authors are not pressured by automated
nits alone.
- stale-issues: marks issues stale after 30 days of inactivity and
closes after a further 7 days. Exempts good first issue, help
wanted, and security labels. A pre-step also auto-applies
'exempt-from-stale' to issues opened by org members/owners/
collaborators, since actions/stale only supports label-based
exemptions. PR processing is disabled (handled by the workflow
above).
* fix: limit PR inactivity feedback to trusted reviewers
Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode)
* fix: count author PR reviews as inactivity responses
Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode)
* feat(web): highlight captured Pod component on chip hover
Wires onPointerEnter/Leave on chip + onFocus/Blur on its × so the matching member overlay gets an outline-focused emphasis on canvas.
* ci: trigger re-run for flaky palette dark-mode test
* fix(web): skip non-mouse pointer events on pod chip hover
Gate onPointerEnter/Leave to pointerType === 'mouse' so a touch tap on a chip no longer flickers the captured-member highlight; also add the CommentTargetOverlay class-wiring test, drop the redundant alpha on the hover outline, and document the non-member fallback path.
Use full workspace installs for blog indexing workflows so root postinstall can resolve workspace build dependencies, and enrich sitemap metadata for blog URLs from frontmatter dates.
Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: remove dead ternary in WORKSPACE_ROOT resolution
Both tools/dev and tools/pack config files had:
ENTRY_DIR_NAME === "dist" ? "../../.." : "../../.."
with identical branches. Since `src/` and `dist/` are siblings under
`tools/{dev,pack}/`, both resolve to the same path. The ternary and
ENTRY_DIR_NAME constant were dead code — simplify to "../..".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Fix workspace root depth
---------
Co-authored-by: Test User <test@example.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: PerishCode <perishcode@gmail.com>
* chore(featured): curate Featured picks down to top 10 across categories
The picker's Featured chip currently surfaces 64 plugins because
`isFeaturedPlugin` (apps/web/src/components/plugins-home/facets.ts)
treats any finite `od.featured` number as featured, and the field
had been adopted incrementally by 51 skills + 13 templates without
a curation step. The result is a "Featured" tab that's effectively
"everything tagged at all" — no editorial signal.
Curate down to 10 picks, allocated to keep the showcase legible:
0.001 skills/deck-swiss-international
0.01 skills/deck-guizang-editorial
0.02 design-templates/magazine-poster [add]
0.04 skills/doc-kami-parchment
0.10 design-templates/web-prototype-taste-brutalist [add od: block]
0.13 skills/video-hyperframes
0.14 skills/frame-glitch-title
0.15 skills/vfx-text-cursor
0.16 skills/frame-logo-outro
0.17 skills/deck-open-slide-canvas
Selection mirrors the html-anything `recommended: 1..10` ranking
(html-anything is the upstream content source for these skills, per
the `od.upstream` field on each SKILL.md). Two of those 10 picks
weren't in the prior featured set at all — `magazine-poster` had no
`od.featured` field and `web-prototype-taste-brutalist` had no `od:`
block at all — so they get added rather than just rebalanced.
Removes `od.featured` from the other 56 files. No UI code change;
the existing `isFeaturedPlugin` logic now reads a curated set
instead of an accidental one.
* chore(featured): align baked plugin manifests with curated top 10
The picker reads `od.featured` from each plugin's
`plugins/_official/examples/<id>/open-design.json` manifest, not from
the SKILL.md frontmatter the previous commit edited. Without this
follow-up the curated set of 10 would be invisible to users — the
picker's Featured chip would still surface 27 baked plugins from the
pre-existing manifests.
Mirror the SKILL.md curation into the baked layer:
removed `od.featured` from 19 manifests:
article-magazine, card-xiaohongshu, data-report,
frame-data-chart-nyt, frame-flowchart-sticky,
frame-light-leak-cinema, frame-liquid-bg-hero,
frame-macos-notification, guizang-ppt, html-ppt,
kami-deck, kami-landing, mockup-device-3d,
open-design-landing-deck, ppt-keynote, resume-modern,
social-reddit-card, social-spotify-card, social-x-post-card
added `od.featured` to 2 manifests:
magazine-poster -> 0.02
web-prototype-taste-brutalist -> 0.10
Verified locally against `daemon /api/plugins`: featured count is
now exactly 10, matching the SKILL.md source of truth from the
previous commit.
* feat(design-files): add filter-by-kind dropdown
The design files viewer already supports grouping and sorting by kind,
but on projects with many files of mixed kinds you still see everything
in one list. Adds a small dropdown next to the group-by toggle that
filters the visible files to one or more selected kinds.
Behavior:
- Dropdown only appears when the project has at least two distinct
kinds; on a single-kind project the control would be noise.
- Multi-select via checkboxes. Empty set means no filter (show all).
- Each row shows the kind glyph, label, and the per-kind file count.
- Trigger label collapses sensibly: "Filter by kind" when none selected,
the single kind's label when one selected, "{n} kinds" when many.
- Filter applies upstream of sort/group/page, so pagination ranges and
the "1-N of M" counter reflect the filtered total.
- Page resets to 0 on filter change so the user doesn't land on an
empty page.
- Selected kinds that disappear from the file list (after a delete or
refresh) are pruned automatically — a stale filter cannot silently
hide everything.
- Popover closes on outside click and Escape.
i18n keys: designFiles.filterBy, designFiles.filterClear,
designFiles.filterCount. All non-English locales inherit via the
existing `...en` spread pattern; zh-CN is the one fully-explicit
dictionary and gets translated values inline.
No behavior change to the group-by toggle, sort, pagination, selection,
or any other existing control. Filtering is layered alongside.
* fix(design-files): prune selection when filter hides selected rows
Codex review flagged that the filter-by-kind change only reset
pagination, not selection. If a user selected files, then narrowed the
filter to a kind that excludes some of them, the now-hidden rows
remained in `selected`. Bulk delete / download still operated on the
full set, so the user could destructively act on rows they could no
longer see — especially risky for the delete path.
Add a useEffect that prunes `selected` to the filtered set whenever
`filteredFiles` or `kindFilter` changes. The empty-filter branch is a
no-op so clearing the filter doesn't disturb any pre-existing
selection — only narrowing the filter prunes.
Picking a chip such as Slide deck or Image loaded a default prompt
into the Home textarea and rendered an overlay with `{{key}}`
placeholders as interactive `<input>` / `<select>` controls. The
overlay controls and the underlying textarea text were laid out
independently:
- Inputs declared `min-width: 8ch` and `Math.max(displayValue.length
+ 1, 10)ch` of width.
- Selects added 18px of right-padding for the dropdown arrow.
- The textarea kept the raw substituted string in a proportional
font.
The two layouts no longer matched column-for-column, so every slot
shifted the textarea text to the left of where it appeared in the
overlay, compounding across the line. Clicking on visible prose to
position the caret hit a different character offset in the textarea
and subsequent typing or deletion landed in the wrong place.
For example, the Slide deck template
Create a {{slideCount}}-slide {{deckType}} for {{audience}} about
{{topic}}. ...
renders with slideCount=10 (~2 ch in the textarea) under a slot
input forced to 10 ch in the overlay — clicking right after the
literal `-slide` placed the caret several characters into `pitch
deck`. The Image / Video / Audio chips with their pre-filled
subject, style, aspect values reproduced the same drift.
Render the inline pills as read-only `<span>`s carrying the exact
substring the textarea shows at that position, mark them
`aria-hidden` so the textarea remains the single labelled control,
and surface every plugin input field — including the ones referenced
inline — in `PluginInputsForm` underneath. Editing flows through the
form, the parent's `updateActiveInputs` already re-renders the
prompt, and the pills stay aligned with the textarea on every
keystroke.
Also drop the now-unused inline helpers (updatePluginInput,
getTemplateInputNames, shouldRenderSlotAsText, inlineFieldType,
fileInputLabel, fileMetadata) and the dead
`.home-hero__prompt-slot-control/input/select/toggle/file/text` CSS
rules.
Verified:
- pnpm --filter @open-design/web typecheck
- vitest run on HomeHero.plugin-picker, HomeHero.rail, and
HomeView.prefill (29/29 pass; tests updated to reflect the new
read-only span + always-on form contract)
- Manual click-to-edit on Slide deck and Image chips in the
pnpm tools-dev web runtime — caret now lands where the user
clicked.
* fix: rename Pets settings entry from 'Customize in Settings' to 'Manage Pets'
- Change pet.composerOpenSettings label to 'Manage Pets'
- Improves clarity and discoverability of Pets management feature
- Label now accurately reflects the destination (Pets settings tab)
- Fixes#1250
* fix: remove pet.composerOpenSettings overrides from non-English locales
All locales now inherit the updated 'Manage Pets' label from en.ts
* fix: add pet.composerOpenSettings to zh-CN locale
zh-CN.ts does not use ...en spread, so it needs explicit key definition
* fix: restore pet.composerOpenSettings translations in all locales with 'Manage Pets' equivalent
Instead of falling back to English, each locale now has the proper translation:
- ar: إدارة الحيوانات الأليفة
- de: Haustiere verwalten
- es-ES: Administrar mascotas
- fa: مدیریت حیوانات خانگی
- fr: Gérer les animaux
- hu: Háziállatok kezelése
- id: Kelola Hewan Peliharaan
- it: Gestisci animali
- ja: ペットを管理
- ko: 펫 관리
- pl: Zarządzaj zwierzętami
- pt-BR: Gerenciar pets
- ru: Управление питомцами
- th: จัดการสัตว์เลี้ยง
- tr: Evcil Hayvanları Yönet
- uk: Керувати вихованцями
- zh-TW: 管理寵物
reloadConnectorStatuses commits its prune setState (and runs the connectorAuthorizationPendingRef mirror effect) between the two awaits in onFocus, so cancelStaleAuthorizations was reading a post-prune ref and never identified anyone as stuck. Capture the snapshot inside onFocus before reloadConnectorStatuses runs, and pass it explicitly so the expired-auth daemon cancel actually fires.
Refs #1909#1354
Saved Project instructions had no post-save read-back surface. The
header control was a bare pencil icon that opened an editor and closed
on Save, so after returning to the workspace a user could not confirm
what was stored, revisit it, or tell whether it was still active
(#1822).
Make the saved state discoverable: once instructions exist, the header
affordance becomes a persistent "Project instructions" chip. Opening it
shows a read-only review panel with a preview of the saved text and an
"Active - included in every message" status, plus a reopen-to-edit
action. Saving lands back on the review panel so the stored value is
read back immediately. The empty state keeps the pencil and opens the
editor directly.
Persistence and project-level system-prompt injection are unchanged.
Closes#1822
Co-authored-by: multica-agent <github@multica.ai>
A friendly stylized chibi dentist in white scrubs and surgical mask.
8x9 atlas (1536x1872 webp) covering idle, run-left/right, waving,
jumping, failed, waiting, running, review states.
`overscroll-behavior: none` on `.newproj-body` prevented trackpad and
mouse wheel scrolling on macOS — the panel only scrolled when hovering
directly over the native scrollbar track. Removing the property restores
normal scroll behavior.
Fixes#1942
* docs(i18n): sync zh-TW README with English version
- Add missing Docker, Nix, and Stay in the loop sections
- Add Desktop data migration guide (OD_LEGACY_DATA_DIR + manual copy)
- Update badge bar with Latest release and Follow @nexudotio badges
- Update At-a-glance table: BYOK endpoint, design systems 72→129,
Media generation row, [ads] reference
- Update OD_DATA_DIR row from "not supported" to full instructions
- All translations follow Taiwan Traditional Chinese conventions
and TRANSLATIONS.md rules
* fix(i18n): link QUICKSTART.zh-TW.md instead of QUICKSTART.md in zh-TW README
Closes#1765
`od plugin publish --to open-design` builds the registry-submission
body from the daemon's `/api/plugins/:id` row. The row carries a
stored `version` plus the full manifest. For project-local plugins —
`generated-plugin/`, snapshots, freshly imported folders — the
stored `version` reads as `'0.0.0'` until the registry handshake
runs and stamps the real version. The manifest's `version`, however,
is the author's real value the whole time.
Before this patch, the publish flow consumed `row.version` directly
and printed `version: 0.0.0` into the registry submission body even
when the plugin's `open-design.json` declared `0.1.0` or any other
non-sentinel version.
Mirror `apps/daemon/src/plugins/marketplaces.ts:298,328` and prefer
the manifest version when the stored row reads as the pre-handshake
sentinel. The fallback chain is now:
1. row.version, when it exists and isn't '0.0.0'
2. row.manifest.version, when it exists and is non-empty
3. row.version (even if '0.0.0'), preserving today's behavior for
plugins that genuinely have no manifest data
4. '0.0.0' literal, when nothing else is available
The fallback chain is conservative: it never substitutes a higher
version that the author didn't already declare somewhere — it just
prefers the manifest over the stored sentinel.
Validation:
- `pnpm --filter @open-design/daemon test -- tests/plugins-publish.test.ts` → green
- `pnpm --filter @open-design/daemon typecheck` → green
Co-authored-by: nicejames <nicejames@gmail.com>
The first-run consent banner had no link to a privacy policy, an
affirmative button ("Help improve") that didn't read as a consent
choice, and a fixed bottom-right card that crowded content on phones.
- Add a "Read the privacy policy" link (external-link icon, accent,
underlined) above the actions, plus a root PRIVACY.md it points to
documenting the telemetry behaviour the modal discloses.
- Rename the CTAs to "Share usage data" / "Don't share" so both name
the action; they stay equal-prominence per the EDPB/GDPR comment.
- Stretch the banner to a bottom-edge bar under 540px with a
safe-area inset so it clears mobile browser chrome.
- Add PrivacyConsentModal tests; sync the new i18n key to every
locale and update the consent-label assertion in App.connectors.
Refs #1756
Walk document.styleSheets so chromatic custom properties on :root/html/body/:host (including @media-nested rules) are hue-shifted via inline setProperty, not just the values returned by getComputedStyle.
Fixes#1393
References: PR #1643 (open, in review) binds the Tweaks toolbar toggle to artifact `.tw-panel` panels via a new `injectTweaksBridge` in `srcdoc.ts` — adjacent surface but different problem. Our fix extends `injectPaletteBridge` to also shift `:root` CSS custom-property colors, which #1643 does not touch.
* fix(web): recover stuck Composio connector when auth is cancelled
When a user closes the Composio system-browser auth window without completing the flow, the connector card stayed in its loading state forever because nothing told the daemon the request was abandoned.
The window-focus refresh now awaits the status poll and, for every connector still in connectorAuthorizationPending whose freshly fetched status is not 'connected', issues cancelConnectorAuthorizationRequest and clears the matching local pending/error UI state. The connected guard is read from the just-returned statuses so a postMessage success that lands just before focus is not racy.
Fixes#1354
* fix(web): gate Composio focus auto-cancel on TTL + cancel response
Require expiresAt to have elapsed before auto-cancelling a pending
Composio auth on focus, and preserve the spinner on a failed cancel
(matching the manual Cancel handler) so the UI never disagrees with
the daemon.
Refs https://github.com/nexu-io/open-design/pull/1909#discussion_r3254152045
Refs https://github.com/nexu-io/open-design/pull/1909#discussion_r3254152046
* feat(web): add manual removal for captured Pod components
Adds `removePodMember` helper and a per-chip × in `BoardComposerPopover`; leaves `comments.ts` untouched (avoid-zone from #1127).
Closes#802
Contract: runs/2026-05-16T08-08-52_open-design_issue-feat/contract.md
* style(web): hide Pod chip × until chip hover
Swaps the unicode × for the existing `Icon name="close"` SVG so the
hit target stays centered, and fades the button in only on chip
hover / keyboard focus for a quieter resting state.
* fix(web): auto-close Pod composer when last chip is removed
Removing the last chip leaves a stale anchor; close so Send cannot attach to elements no longer visible.
* refactor(web): extract BoardComposerPopover and Pod-member reducer
Moves the popover to its own module and lifts the chip-removal reducer into a pure `applyPodMemberRemoval` so unit tests exercise the real code path and the popover's export is no longer test-only.
* fix(web): rebuild Pod anchor when a member is removed
Without this, the popover keeps the original union bbox / selector / label after each chip removal, so a subsequent Send to chat anchors the comment to elements no longer in the Pod.
* fix(web): render every captured chip and scroll on overflow
The previous slice(0, 6) cap left chips beyond the sixth invisible and undeletable. Render the full list inside a 132px-tall scrollable strip.
Clicking Edit on a built-in skill silently issued PUT /api/skills/:id,
which the daemon stores as a user-owned shadow and then hides the
built-in entry from the Settings list. From the user's perspective the
row they just edited disappears with no explanation.
Arm an inline override-warning banner on Edit for source==='built-in'
skills, mirroring the existing inline delete-confirm pattern. The edit
form only opens after the user explicitly confirms. User skills bypass
the warning and edit directly. No daemon or listSkills contract change.
Fixes#1378
The Routines list only exposed Run now / Pause / History / Delete, so any
change to a routine required deleting and recreating it. The daemon already
serves PATCH /api/routines/:id; only the web entry point was missing.
Add an Edit button on each routine row that pre-populates the create form
from the stored schedule and target via a new formFromRoutine helper, and
branch the submit handler on editingId so it PATCHes /api/routines/:id
with the updated fields instead of POSTing a new routine.
Fixes#1373