Add the 0.6.1 entry to the four changelog locales and bump the web and desktop package versions to 0.6.1.
Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* docs(license): name Index Labs (Hong Kong) Limited as the producer (MUL-7558)
The LICENSE and NOTICE credited "Multica, Inc.", which is not a registered
entity. Replace it with the company that holds the rights, define "the
producer" (used throughout Part I but never defined), and point to where
commercial licenses and branding waivers are requested.
Co-authored-by: multica-agent <[email protected]>
* feat(web): add licensing and privacy pages, describe Multica as source-available (MUL-7558)
- /licensing: plain-language licensing FAQ with the rule of thumb and common
scenarios, in en/zh/ja/ko.
- /privacy: privacy policy based on what Multica Cloud actually collects;
the Contact Sales privacy link now points here instead of /about.
- About: new "Who's behind Multica" section (team story + contact routes;
team member cards left for later).
- Replace "open source" / "fully open source" with source-available
wording across the landing copy, page metadata, JSON-LD, docs, READMEs,
and the Helper agent prompt, since the license restricts hosted use.
- Contact Sales consent copy says "Multica" instead of "Multica, Inc.".
- Reserve the "licensing" workspace slug for the new top-level route.
Co-authored-by: multica-agent <[email protected]>
* fix(web): drop the Slack users section from the licensing page (MUL-7558)
Co-authored-by: multica-agent <[email protected]>
* fix(web): add the commercial-use FAQ in ja/ko and sharpen the source block headline (MUL-7558)
- ja and ko override faq.items wholesale, so the new "Can I use Multica
commercially?" entry (the FAQ's only link to /licensing) was missing
there. Add it, plus a test that keeps every locale's FAQ in step.
- The source block headline now states the value ("Every line, on your
terms.") instead of repeating the license category.
- Privacy policy: list apps connected through Composio among the
integrations that receive data.
Co-authored-by: multica-agent <[email protected]>
* fix(web): align the privacy policy with what the product actually does (MUL-7558)
Privacy review found promises the code does not keep:
- Workspace deletion removes content from the service, but uploaded files
are not yet erased from object storage and backups keep copies. Say so,
and give an email route for erasing files.
- AI: coding agents send prompts, code, and tool results to their own model
providers; only the Cloud assist features use a provider we pick. Scope
the training promise to Multica.
- Crash reports: redaction filters recognizable emails and credentials in
the error message only, not every field.
- Self-hosted: list the snapshot fields (including the random deployment
ID), and note that AI, integrations, and analytics follow the operator's
configuration.
- Sharing: cover workspace members, admins, and authorized agents; move
legal and M&A disclosures out of the service provider list.
- Add legal bases, consent withdrawal, and the right to complain, plus
retention for billing records and analytics.
Co-authored-by: multica-agent <[email protected]>
* fix(web): build trust-page test dictionaries through createLandingDict (MUL-7558)
main now passes a docs href to each landing dictionary factory, so the
test's single-argument calls failed typecheck. Build the dictionaries the
way the app does, mark the DOM-free test as node, and bring the new French
mobile-app doc in line with the source-available wording.
Co-authored-by: multica-agent <[email protected]>
* fix(web): stop overpromising on data handling and align trust copy with product terms (MUL-7558)
- The commercial-use FAQ now names both license triggers in every locale
(offering Multica to outside users, and embedding it in a product you sell
or distribute); ja and ko read as hosted-only before.
- Replace "your data never leaves your network" and "code never passes
through Multica servers" with what actually happens: agents run on your
machines, workspace content is stored by Multica, coding tools send
prompts to their model providers, and self-hosting keeps workspace data on
your servers. Links to the privacy policy.
- New and changed copy follows the terminology conventions: issue ->
任务/タスク/태스크, agent run -> 运行/実行/실행 (Run in English),
workspace -> 工作区, onboarding -> 上手引导.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* docs(i18n): translate documentation corpus to French (MUL-7637)
Add French (.fr.mdx) translations for all 45 docs pages plus meta.fr.json
navigation, mirroring the English source. Product and UI terms follow the
in-app French locale at packages/views/locales/fr/ (issue -> tâche,
run -> exécution, autopilot -> automatisation, Chat -> Discussion,
Settings -> Paramètres, etc.). Status and role identifiers stay lowercase
English per the conventions glossary, with the French UI label shown once
where they are defined.
Co-authored-by: multica-agent <[email protected]>
* feat(docs): serve French docs locale (MUL-7637)
Register fr in the docs i18n config and add the French Fumadocs chrome,
language-switcher label, and home hero copy. Fumadocs maps fr to Orama's
built-in French tokenizer, so search needs no custom localeMap entry.
Co-authored-by: multica-agent <[email protected]>
* feat(i18n): link French UI to French docs (MUL-7637)
Route French users from the landing page, runtime guides, the autopilot
webhook filter help, and the Slack/Lark/DingTalk/Telegram setup links to
/docs/fr. The four integration tabs and the runtime helper each carried a
copy of the same locale-prefix ternary; they now share docsLocalePrefix.
Co-authored-by: multica-agent <[email protected]>
* fix(i18n): localize remaining docs entry points (MUL-7637)
French users still reached English docs from the Help menu, the Agents and
Skills "Learn more" links, and the landing footer. The views links now use
docsLocalePrefix; the landing dictionaries take the docs href from
docsHrefForLocale, so locales that reuse another dictionary (French reuses
English) still link to their own docs.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
Add the 2026-09-21 changelog entry in all four locales and set the web and desktop app versions to 0.5.1. Steering is left out — it is not shipping today.
* docs(changelog): add v0.5.0 release entry (2026-09-18) (MUL-7481)
Adds the 0.5.0 changelog entry to all four landing locales and bumps the
web and desktop app versions.
Range v0.4.44 → 594ff89 (55 PRs). Minor bump because the release ships
full French product UI, which is worth announcing on its own.
Left out of the changelog, as agreed on MUL-7481: Triage (triage_v1 not
rolled out), the plugins_v1 / billing_workspace_subscriptions gate error
semantics, UI Lab, CI/test/docs, prompt and skill changes, the Issue
status category migration, and internal performance work.
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): retitle the v0.5.0 entry around agent behavior (MUL-7481)
The title listed the top of `features` and carried a small feature
(Autopilot schedule editing) while leaving out the day's agent work.
Drop the small feature, keep the French UI, and put the agent side in:
runs are both steadier (Grok/Pi/Copilot/Codex/Cursor/Hermes, durable
terminal reports) and leaner — a resumed agent no longer re-reads the
whole Issue and every comment (#8377, #8488). That last one now has its
own improvements line so the title stays backed by the entry.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Bohan-J <[email protected]>
* feat(i18n): add French locale
Adds `fr` as a supported UI locale for web and desktop.
- 25 translated resource files under `packages/views/locales/fr/`, at full
parity with the English source (`locales/parity.test.ts` passes).
- Registers the locale in `SUPPORTED_LOCALES`, the views resource bundle, the
settings language picker, and the `HTML_LANG` maps for web and desktop.
- Translates the celestial workspace name list. Proper nouns with no
established French form are left unchanged.
- Landing page: adds the `FR` label but leaves the public `locales` array at
four, since the marketing copy is not translated.
- Onboarding templates map `fr` to English content, matching the existing
fallback for locales without authored templates.
- `pick-locale.test.ts` used `fr` as its example of an unsupported locale;
those assertions now use `es`, plus a new case asserting `fr-FR` resolves
to `fr`.
Terminology follows the glossary in
`apps/docs/content/docs/developers/conventions.mdx`. The UI uses the formal
register (vouvoiement) consistently.
* fix(i18n): translate the backlog status label in French
The French locale translated six of the seven issue statuses and left
`backlog` as "Backlog". The other locales translate all seven — `待规划`,
`백로그`, `バックログ` — so the odd one out was an oversight, not a choice.
`conventions.mdx` does say role and status enums stay in lowercase English,
but that rule is about surfacing the identifier inside prose ("switched to
`in_progress`"), not about the `status` display block, which every shipped
locale translates.
"À planifier" reads as a pair with "À faire" for `todo`, and matches what
`待规划` conveys: the issue exists but is not scheduled yet.
* fix(i18n): realign the French locale with main
Rebasing on main surfaced two drifts that the parity test caught:
- `settings.json` gained `shortcuts.fixed.open_settings` upstream; added
the French string.
- `search.json` lost its `pages.*` block in #7054, which now generates the
command palette's Pages group from the nav registry. Dropped the eight
French keys that no longer have an English counterpart.
* fix(i18n): resync the French locale with main
157 English keys had no French translation and 97 French keys no longer had an
English counterpart. The parity suite caught all of them.
Added: custom issue statuses (76 keys), the Telegram integration (55), issue
revision conflicts and the status catalogue (20), plugin panels and hook
activity (24), revoked agent access in chat (4), autopilot run quota and the
worktree execution mode (2).
Removed: 97 keys upstream has dropped or renamed, most of them under plugins.*.
Two shape changes worth naming, because a key count does not show them:
- plugins.install used to be the string "Install" and is now an object with
title/description/placeholder/review. A locale that keeps the old shape does
not have a missing key, it has a key of the wrong TYPE, and parity alone
stays silent about it — only writing to it fails.
- telegram_bind mirrors slack_bind key for key, so it is derived from the
existing translation with the service name substituted rather than written
again. Two texts that say the same thing drift when they are typed twice.
Register: vouvoiement throughout, consistent with the rest of the locale.
* fix(i18n): complete French locale integration and refresh translations
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Lambda <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Cover v0.4.43 -> 20f20d6bc7 in the four landing
locales and bump the web app version to 0.4.44.
Patch bump: the range is channel work (DingTalk, Lark, WeCom), the Issue status
model, and incremental changes to existing surfaces — nothing that warrants a
standalone announcement.
Left out as unreachable for users after this release: the Triage groundwork
behind triage_v1 (key and helper only, no call sites), the core prompt and
built-in skill guidance changes, and the migration, index, test and CI work.
Self-hosted anonymous telemetry is included, together with its DO_NOT_TRACK
opt-out, since it is on by default and administrators need to know.
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Cover the v0.4.43 -> HEAD range in the four landing locales and bump the web app version to 0.4.44.
Patch bump: the day is mostly channel work (DingTalk, Lark, WeCom) plus incremental changes to existing surfaces, with nothing that warrants a standalone announcement.
Triage infrastructure behind the triage_v1 flag, the database migration and index work, and the test/CI maintenance PRs are left out: none of them is something a user can reach after this release.
Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): add v0.4.43 release entry (2026-09-11)
Add the 0.4.43 changelog entry to the four landing locales and bump
apps/web to 0.4.43. Range: v0.4.42 -> 2441638c (origin/main), which is
Wall-E's confirmed da7fc439 plus #8309 merged after the confirmation.
MUL-7291
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): trim the v0.4.43 title to performance improvements
Per review on MUL-7291: drop the tool-output truncation notice and the
long-run stability items from the title and use performance improvements
instead. The corresponding feature/fix lines are unchanged.
MUL-7291
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): cover comment and description annotations in v0.4.43
Range extended to d4493208 (current origin/main): #8274 adds comment and
description annotations, so the entry gains two feature lines and the
title leads with them. #8313 and #8316 are internal and stay out.
MUL-7291
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(web): show a 14-mark provider row in the landing hero (MUL-7251)
The hero listed five runtimes as logo + name pairs, which read as the whole
list rather than a sample of a catalog that is now past twenty. Replace it
with a single row of monochrome marks under a link that states the real
count and points at the provider comparison in the docs.
The marks are the vendors' official artwork reduced to one colour so the row
reads as one system on the photographic backdrop: the app tile dropped so the
glyph inherits currentColor, and for Trae and Hermes — raster-only art — the
image flattened to white. Sizes are tuned per mark because no two vendors fill
the icon box the same way.
Co-authored-by: multica-agent <[email protected]>
* fix(web): pin the hero provider row to 14 x 1 or 7 x 2 (MUL-7251)
Free `flex-wrap` split the 14 marks 13 + 1 near 834px and 12 + 2 near 768px,
and one orphan on the second row reads as a broken layout. Give the row
explicit column counts instead — fourteen once a single row fits, seven below
that — so every width lands on a full row and the columns carry the spacing.
Also swap OpenClaw for OpenCode in the row, as asked. Its two greys become two
opacities of one colour, keeping the solid mark from outweighing the outline
glyphs beside it.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
The landing hero's "works with" list still showed the retired Gemini CLI. Align it with the README and runtime provider list by showing Antigravity with its official mark as a monochrome inline SVG, matching the other hero logos.
* docs(changelog): add v0.5.0 release entry (2026-09-09) (MUL-7190)
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): retarget the 2026-09-09 entry at v0.4.42
Bump the release to a patch instead of a minor, tighten the entry title
in all four languages, and correct the per-thread queueing line: threads
get their own pending slot, but ClaimAgentTask still serializes runs per
(issue, agent), so they do not run without waiting for each other.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: J <[email protected]>
#8114 landed one commit after the confirmed release HEAD, so the 0.4.41
entry shipped in #8115 did not describe it. Add the Settings grouping,
the single integrations page and the Preferences consolidation to all four
languages, and swap the lowest-impact title item for the reorganization.
Extended range: v0.4.40..53575a2a9 (MUL-7115). Version stays 0.4.41;
apps/web/package.json is already correct from #8115.
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Adds the 2026-09-07 entry to all four language sites and bumps apps/web to 0.4.41.
Range: v0.4.40..d753c7d7840f56cc82f82a6cfe90bdc52e06dd19 (MUL-7115).
Excludes the two changes users cannot perceive: the agent runtime brief /
built-in skill guidance (#8102) and GitHub PR refresh replica routing (#8096).
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(i18n): finish the MUL-5703 glossary rollout across ja/ko and docs (MUL-6896)
MUL-5703 settled the rule — the issue entity is translated (任务 / タスク /
태스크) and the agent run keeps `task` / 作業 / 작업 — but only ever landed in
the zh-Hans UI. Everything else drifted:
- ja/ko shipped 39 UI strings that still spelled the entity in English
(`issue`), or as イシュー / 課題 / 이슈.
- zh and ja spelled the *run* with the *issue* word in the project worktree
copy and in the MCP delete dialog — the exact collision MUL-5703 exists to
prevent. ko already had this right (작업).
- ko wrote runtime-health "issues" as 이슈, so a machine card read as the
entity. zh/ja use 异常 / 問題, and ko's own health_clear already said 문제.
- `conventions.ja.mdx` / `conventions.ko.mdx` still documented the *pre*-
MUL-5703 rule ("issue stays English"), contradicting the en/zh glossary.
- The docs prose sweep the glossary calls "being swept separately" never
happened: ~471 English `issue` in zh, ~414 イシュー in ja, ~418 이슈 in ko.
Sweeping the entity into 任务 / タスク / 태스크 forces the run to move too —
zh docs wrote it 执行任务, ja 実行タスク, ko 실행 태스크, all of which collide
with the entity word once the entity lands there. So the run is rewritten to
`task` / 作業 / 작업 in the same pass, matching what each UI already shows.
Standalone labels keep the English source's capitalisation (`Task` page
title and links); inline mentions are lowercase, like `skill`.
Also corrects the en source for two squad strings: squads receive *issues*
and the leader delegates *sub-issues*, not "tasks" / "sub-tasks". The three
translations already said the entity, so only en moves.
Not touched: the landing changelog archive, the generic English noun "task"
in chat/onboarding marketing copy, `issue` in the "problem" sense, and the
unrelated structural drift in the ja/ko conventions pages (they are missing
the "Runtime display names" section from an older sync).
Verified: 4907 views tests pass including locale parity, typecheck and lint
clean, go build plus the integrations/cli suites pass, and no doc anchor
broke (checked before/after).
Co-authored-by: multica-agent <[email protected]>
* fix(docs): sweep the last old run-word leftovers in zh copy (MUL-6896)
Review caught the docs home page still linking "[执行任务](/tasks)" while ja/ko
had already moved to 作業 / 작업. Four more of the same class turned up next to
it: the same link in README.zh, "任务生命周期" in VISION.zh (en: "the task
lifecycle"), and two redundant "task(task)" glosses the sweep produced where
the source had written "执行任务(task)".
With these, `执行任务` / `実行タスク` / `실행 태스크` are gone repo-wide, so the
"prose now follows the table" line added to conventions.mdx is true.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): add v0.4.36 release entry (2026-08-28)
Add the 2026-08-28 changelog entry in en/zh/ja/ko and bump apps/web to 0.4.36.
MUL-6787
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): drop unreleased billing items from v0.4.36 entry
Cloud billing is still in staging and has never shipped, so the Issue count
limit and its upgrade path must not appear in the public changelog. Retitle
around the changes that are actually live.
MUL-6787
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Preload the above-the-fold landing background so it starts downloading alongside
the product preview instead of after it, shortening the black-fallback window on
cold loads. Also declares an explicit `sizes="100vw"` on the backdrop and moves
the product preview off the deprecated `priority` prop onto `preload`.
Co-authored-by: Binteng <[email protected]>
* docs(changelog): add v0.5.0 release entry (2026-08-24) (MUL-6642)
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): omit gated WIP release notes (MUL-6642)
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): retitle release entry and use 0.4.33 (MUL-6642)
Replace the vague "local environments" title item — it named a
contributor-only `make up` change with no user-visible surface — with the
localized task failure messages users actually see, and drop that feature
line from the entry.
Keep the release on the 0.4.x line as a patch bump over v0.4.32.
Also restore the ja/ko indentation the previous commit shifted, so both
files diff as pure additions again.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: J <[email protected]>
* docs(changelog): add v0.5.0 release entry (MUL-6474)
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): reclassify v0.4.31 release (MUL-6474)
Co-authored-by: multica-agent <[email protected]>
* docs(changelog): name what shipped in the v0.4.31 title (MUL-6474)
The v0.4.31 title was three evaluative phrases -- "smoother agent runs",
"safer automation", "reliable Windows builds" -- none of which names a
thing a reader can recognize. House style for changelog titles is 2-3
parallel noun phrases naming the actual feature, page, or surface.
Replace all four locales with the three changes a reader would notice:
the run confirmation before an assigned Issue leaves the backlog, the
Webhook event filters, and the Windows build fix. Terms follow the
existing glossary and each entry body.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(agent): add Dim (DimCode) ACP runtime
Add Dim (dimcode, the `dim` CLI) as a first-party agent runtime, driven
over the ACP (Agent Client Protocol) transport via `dim acp`.
Key integration points:
- New dim backend (pkg/agent/dim.go): spawns `dim acp`, performs the
ACP initialize/session/new handshake, and raises the runtime's
hardcoded read-only permission preset to full-access (plus agent mode)
via session/set_config_option before the first prompt — without it every
file write is denied by a capability rule. Model override uses
session/set_model; the model catalog is read from session/new.
- Session resume is intentionally skipped: Dim binds sessions to the
creating process, so a later process's session/load is rejected with
"held by another process" even after a clean session/close. The backend
always starts a fresh session and reports ResumeRejected so the daemon
classifies the run correctly. A best-effort session/close keeps Dim's
own session list free of orphaned entries.
- Registration: SupportedTypes whitelist, New() factory, launch header,
daemon probe (MULTICA_DIM_PATH / MULTICA_DIM_MODEL), default agent
command name, protocol_family CHECK migration (255), model discovery,
MCP config support, metrics label, and runtime display.
- Docs/UI: provider logo, landing i18n (en/zh/ja/ko), providers and
install-agent-runtime docs, README, CLI_AND_DAEMON, SELF_HOSTING.
- Tests: dim backend unit tests (fresh session + resume behavior), shared
ACP deliverable case, logo and MCP-support tests.
* feat(views): use official DimCode app icon for Dim provider logo
Replace the placeholder inline SVG with the official DimCode desktop
client app icon (from /opt/DimAgent/resources/build/icon.png, resized
to 128px), imported as a static asset the same way the Qwen Code mark
is handled.
* test(agent): add real dim ACP smoke tests (agentintegration)
Two gated tests behind MULTICA_RUN_REAL_AGENT_SMOKE=1:
- TestDimRealACPSmoke: full end-to-end against real `dim acp` —
initialize, session/new, set_config_option (permission/mode), prompt,
and assert completed output.
- TestDimRealResumeRejected: pass a fake ResumeSessionID, assert the
backend starts a fresh session (no session/load), and reports
ResumeRejected=true so the daemon classifies the run correctly.
Verified against dimcode 0.3.2 and 0.3.8.
* fix: renumber dim migration 265 → 271 (upstream added 265-270)
* fix: renumber dim migration to 272 (upstream added 271)
* fix(agent): dim cross-run resume via session/load + review fixes
Address review feedback on the Dim ACP runtime:
- #1 Deliver runtime brief to Dim: add dim to the AGENTS.md provider
list (verified dim 0.3.8+ reads AGENTS.md from the session cwd).
- #2 Cross-run session continuity: dim 0.3.10+ releases its per-process
session lock ~5s after the owning process exits, so resume now goes
through the standard ACP session/load (same as traecli/kiro/grok)
instead of always starting fresh. A loaded session retains its
permission/mode, so set_config_option runs only on fresh sessions.
Add a two-run regression (TestDimRealCrossRunResume) proving run B
recalls context established only in run A.
- #3 Process lifecycle races: the deferred cleanup now cancels the run
context before cmd.Wait (a child ignoring stdin EOF no longer hangs
Result delivery) with a bounded force-kill fallback; the success path
waits for the final prompt notification with a grace window instead
of a non-blocking read that could miss the last message.
- #4 Isolate model discovery by executable path (discoveryCacheKey).
- #6 Real smoke test now writes a sentinel file to prove full-access is
effective; a best-effort session/close is sent before teardown.
- #7 gofmt.
* test(agent): add dim process-lifecycle regressions (review #3)
- TestDimCleanupKillsHangingChild: a child that ignores stdin EOF and
SIGTERM after an early failure is force-killed within
dimProcessWaitTimeout, so Result still closes.
- TestDimPromptMissingNotificationStillCompletes: when session/prompt
returns without stopReason (onPromptDone never fires), the bounded
final-notification wait falls through and Result completes instead of
hanging.
* fix(migrations): renumber dim runtime_profile migration 272 → 273 (upstream added 272)
* fix(agent): review round 2 — process tree, quiescence, fail-closed, version check
Address all remaining blockers from the second review:
- #1 Require dim >= 0.3.10 (version check at initialize); bounded retry
on session/load when the lock is not yet released instead of silently
starting fresh; integration test no longer sleeps before resume.
- #2 Process-tree cleanup: configureProcessGroup + signalProcessGroup +
waitProcessGroupGone; cmd.Cancel returns nil so we own all signalling;
regression asserts the descendant PID is gone.
- #3 Notification quiescence: onActivity + waitForACPNotificationQuiescence
(same as hermes); Dim equivalent of the late-final-notification test.
- #4 Permission fail-closed: set_config_option runs on BOTH fresh and
resumed sessions; on config failure, session/close is sent before
returning so a partially configured session is not resumed; regression.
- #5 Migration renumbered 273 → 274 (upstream added 273).
- #6 AGENTS.md mapping regression, two-executable cache isolation test,
session/close unit assertion, smoke test now executes a command.
- #7 README.md + CLI_AND_DAEMON.md updated with 22 CLIs including Dim.
* chore: remove temporary review actions doc (not for PR)
* fix(migrations): renumber dim migration 274 → 310 (upstream added 274-309)
* review: fix retry break scope, dedup isACPSessionNotFound comment, update stale notes
* fix(agent): pass *exec.Cmd to signalProcessGroup/waitProcessGroupGone (upstream signature change)
* fix(agent): close session on set_model failure + resume regression test
Audit-found gaps from self-review:
- set_model failure now sends session/close before returning (same as
set_config_option failure) — reviewer #4 said 'permission, mode, or
model'.
- Add TestDimConfigFailThenResumeReestablishes: run A config fails →
session/close sent → run B resumes and re-applies set_config_option
(fail-closed), completing successfully.
- Fix two stale comments that contradicted the code (config block now
runs on both fresh and resumed sessions).
* fix(agent): add WaitDelay, use labeled break in retry loop
Self-audit improvements:
- Add cmd.WaitDelay=10s for consistency with claude.go (hard backstop
if a process somehow survives SIGKILL).
- Use labeled break (break loadRetry) so runCtx cancellation during the
retry delay exits the for loop directly, not just the select.
- Update PR description: migration 273→310, set_config_option now
re-applied on both fresh and resumed sessions.
* fix(migrations): renumber dim migration 310 → 313 (upstream added 310-312)
* fix(migrations): remove stale 313 dim migration (replaced by 314 after upstream added dsh at 313)
* fix: DSH compatibility + thinking levels + reviewer round 3
Systematic fix of all 39 items from the compatibility gap analysis:
Rebase + migration:
- Rebase to latest upstream/main (resolves all DSH conflicts)
- Migration 314_runtime_profile_add_dim (whitelist includes both dsh + dim)
- Remove stale 313 dim migration
DSH + Dim coexistence (provider lists, counts, docs):
- SupportedTypes, config.go, metrics labels: both dsh + dim
- i18n (4 files): count 23, lists include DSH + Dim + Oh-My-Pi
- README.md/README.zh.md: count 23
- CLI_AND_DAEMON.md: 23-row table
- environment-variables.mdx (4 langs): MULTICA_DIM_PATH/MODEL callout
- display.ts, mcp-support.ts, types/agent.ts, provider-logo.tsx: both dsh+dim
Thinking levels (was MISSING — dim supports thought_level):
- dim.go: call applyACPEffortOption after set_model
- thinking.go: add dim to acpCatalogThinkingProviders
- dim.go: retain sessionResult for effort option
Reviewer round 3:
- Version check fail-closed: empty/malformed → reject (not allow)
- Cache test through ListModels call site with fake executables
- set_model failure regression test
- Test companions: sidecar_manifest_test, runtime_config_test add dim
Code quality:
- agent.go: fix unreachable duplicate return (rebase artifact)
- dim.go: labeled break in retry loop, WaitDelay, session/close on set_model fail
* fix: deliverable test fake version + provider-logo rebase artifact
* fix(migrations): renumber dim migration 314 → 315 (upstream added 314_workspace_mcp_config)
* fix(migrations): renumber dim migration 315 → 319 (upstream added 315-318)
* fix: Windows process tree, MinVersions, retry tests, migration 327
Address all 5 remaining blockers from review round 4:
1. Windows process-tree ownership: use startOwnedProcessTree instead of
cmd.Start(), releaseProcessGroup in cleanup defer. This attaches the
child to a Job Object on Windows (no-op on Unix), ensuring descendants
are captured and terminated.
2. Migration renumbered 319 → 327 (upstream added 319-326).
3. Add dim: 0.3.10 to MinVersions in version.go so the daemon registers
old Dim binaries as offline and refuses triggers (defense in depth on
top of the ACP agentInfo.version check in dim.go).
4. Session-lock retry contract tests: success after bounded retries,
exhaustion without falling through to session/new. Fake script
supports DIM_LOAD_HELD_N (held for N calls then succeed) and
DIM_LOAD_HELD_ALWAYS (always held).
5. README.md:206 20→23 runtimes. PR description updated.
* test: add missing reviewer-requested regressions (round 4)
- Windows Job Object regression: dim_windows_test.go proves the Dim
backend captures and terminates descendants via startOwnedProcessTree
(Windows-only build tag; companion to TestStartOwnedProcessTree).
- MinVersions registration: add dim test cases to TestCheckMinVersion
(0.3.10 ok, 0.3.9 rejected, invalid rejected).
- Retry cancellation: TestDimSessionLoadRetryCancelled cancels the
context during the retry delay, asserts no session/new fallback.
* fix: sync upstream Command signature changes + restore dim additions
Upstream changed ListModels/discoverXxxModels/detectCLIVersion to accept
Command (struct with Path+Prefix) instead of string. The previous rebase
kept our old-signature versions, causing widespread compile failures.
Restored all affected files from upstream, then re-applied dim-specific
additions:
- agent.go: dim in SupportedTypes, New(), launchHeaders
- hermes.go: isACPHeldByProcess + -32002 in isACPSessionNotFound
- thinking.go: dim in acpCatalogThinkingProviders
- models.go: dim case in ListModels + discoverDimModels
- version.go: dim in MinVersions
- All dim test files preserved (dim_test.go, dim_integration_test.go,
dim_windows_test.go, models_test.go dim cache test)
* fix: rebase to latest main, migration 327→341, resolve DetectVersion conflict
* fix: remove unused os/exec import in dim_windows_test.go
* fix(migrations): renumber dim migration 341 → 342 (upstream added 341)
* fix: use Command.exec instead of exec.CommandContext (upstream GH #7046)
Upstream's TestOnlyLaunchGoSpawnsRuntimeProcesses requires all backends
to build processes through Command.exec (launch.go), so a custom runtime's
fixed_args are carried into the subprocess. Dim was the only backend still
using exec.CommandContext directly.
* fix: launch prefix policy + remove empty test files + deterministic retry tests
Address all 4 findings from review round 5:
1. Add "dim": dimBlockedArgs to launchPrefixBlockedArgs so protocol-breaking
flags (--help/--auth-setup/--remote) are filtered from fixed_args.
2. Remove 77 zero-byte *_test.go files accidentally added at repo root.
3. Make retry tests deterministic: TestDimSessionLoadRetryCancelled waits
for the first session/load request before cancelling (no fixed sleep);
TestDimSessionLoadRetryExhausted asserts exactly 4 load attempts.
4. PR description will be updated separately.
* fix: rebase to latest main, migration 342→343 (upstream added mcode at 342)
* chore: trigger CI
* chore: refresh PR
* fix: restore mcode that was lost during merge conflict resolution
Merge took 'ours' side which predated mcode. Restore mcode in:
- SupportedTypes, New() factory, launchHeaders
- agent_supported_types_test.go want map
- metrics/labels.go
- config.go defaultAgentCommandNames
- migration 343 up/down whitelists
* fix: restore mcode probe in agents_probe.go + agent-cli-command-names.txt
* fix: restore all remaining mcode references lost during merge
Files fixed:
- README.md: mcode row in runtimes table
- mcp-support.test.ts: mcode assertion
- config.go: mcode in Agents comment + error message
- runtime_config.go: mcode in AGENTS.md case
- version.go: mcode in MinVersions
- version_test.go: mcode test cases
Verified: no file has fewer mcode references than upstream.
* fix: restore mcode in environment-variables docs + CLI_AND_DAEMON ACP list
- environment-variables.{mdx,ja,ko,zh}: restored 'MiniMax Code' in the
QwenPaw model-variable sentence
- CLI_AND_DAEMON.md: added MiniMax Code to ACP-family list + loadSession
fallback description
Verified: every changed file now has >= mcode references vs upstream.
* fix(migrations): renumber dim migration 343 → 344 (upstream added 343)
* fix(migrations): renumber dim migration 344 → 348 (upstream added 344-347 plugin migrations)
* fix(migrations): update down migration comment 344 → 348
* chore: retrigger CI (flaky TestOpenclawDiscoveryCacheConcurrentPreparations — upstream test, passes locally x5)
* fix(migrations): renumber dim migration 348 → 352 (upstream added 348-351)
* fix: Dim launch-prefix regression test + version.go comment repair (review #6)
- Add TestDimLaunchPrefixFiltersBlockedFlags: proves allowed prefix
reaches command before acp, and --help/--auth-setup/--remote/-h are
stripped from Dim's launch prefix.
- Add dim to TestLaunchPrefixReachesACPFamilies family list.
- Repair version.go: dim's cross-run session/load comment was appended
to mcode entry during a rebase conflict; restore two independent
accurate comments.
* fix(migrations): update down migration comment 348 → 352
* chore: retrigger CI (backend-tests stuck 50+ min)
* fix(migrations): renumber dim migration 352 → 362 (upstream added 352-361)
* fix: use logAgentCommand for dim argv logging (upstream redaction requirement)
Upstream's TestOnlyLaunchGoLogsAgentCommandArgs enforces that all
runtime argv logging goes through Config.logAgentCommand so sensitive
args are redacted. dim.go was still using Logger.Info directly.
* fix(migrations): renumber dim migration to 370
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(telegram): add Telegram channel integration
Co-authored-by: multica-agent <[email protected]>
* chore(telegram): rebase onto main and renumber migrations to 366-367
349/350 collided with main's agent_task_queue_chat_terminal_resume_index
and agent_task_queue_chat_retired_session_index. Renumber the Telegram
origin pair to the next free prefixes, update the cross-references inside
the SQL comments, and point telegram_origin_migration_test.go at the new
filenames. Contributor-facing behavior is unchanged.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Bohan-J <[email protected]>
* chore(assets): convert web and docs bitmaps to WebP
The repo carried 21.7MB of PNG/JPG and zero modern formats. next/image
already serves visitors AVIF/WebP, but the raw masters are still paid for
on every clone, every Docker build context, and every deploy upload.
Re-encode with cwebp and cap the source width at the largest size any
layout can request: 1920px for the decorative landing backgrounds (they
render behind content under object-cover), 2640px for the landing hero
(2x its 1320px slot), 1600px for screenshots (2x the ~720px docs and
use-case column). EXIF goes with it — the four feature backgrounds were
144dpi print exports.
Encoder choice is per image rather than uniform. Most compress best as
lossy; two of the docs screenshots are flat palettized PNGs that encode
smaller with `-lossless -z 9`, so they use that instead. Screenshots are
q80 to keep UI text crisp, decorative art q72.
apps/web/public 10.70MB -> 1.57MB
apps/docs/public 7.47MB -> 2.44MB
Verified: 1:1 crops against losslessly-downscaled references show no
visible difference; all 266 image references in the tree resolve; docs
prerenders all 177 pages and the landing/use-case pages render with no
broken images.
Co-authored-by: multica-agent <[email protected]>
* chore(assets): drop the duplicated README hero image
docs/assets/hero-board.png was byte-for-byte identical to the docs site's
workspace-overview screenshot — 633KB stored twice. Only the two READMEs
referenced the copy, so point them at the canonical file instead.
Co-authored-by: multica-agent <[email protected]>
* chore(assets): losslessly recompress app icons
oxipng -o max --strip safe over the app icon PNGs: -640KB with pixels
untouched (verified by comparing lossless-WebP encodes of the before and
after of every file — all 16 identical).
Deliberately not switched to WebP and not regenerated: the prerendered
sizes under apps/desktop/build/icons/ exist because electron-builder's
auto-derivation silently shipped only the 1024x1024 source in the v0.2.31
.deb (#2515), and the PWA manifest and .icns/.ico containers want PNG.
Filenames, dimensions, and the electron-builder config are unchanged, so
packaging behaves exactly as before.
Co-authored-by: multica-agent <[email protected]>
* ci: gate pull requests on a 300KB image budget
Keeps the previous three commits from being undone one screenshot at a
time. A bitmap added — or grown — past 300KB fails the check until the PR
description carries an `Oversized image exemption:` line saying why.
Shrinking an oversized file always passes: the check compares against the
size at the base ref, not just the absolute size.
Path-filtered to PRs that touch a bitmap, so the full-history checkout it
needs to diff against the base is only paid for when it is relevant.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Lambda <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(views): widen IssueStatus to open key, resolve category at render sites
Board columns, swimlane columns and hidden-column panels are CATEGORIES,
not status keys, so adding a custom status never adds a column. Retyped
those to IssueStatusCategory and made every presentation lookup
(STATUS_CONFIG, i18n labels, status icons) resolve the key to its
category first. Status filters stay in concrete keys and map back to
columns via the catalog.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
* feat(views): catalog-driven status picker and per-status labels
The picker now offers the workspace catalog's active statuses grouped by
category, instead of the 7 hardcoded built-ins, and falls back to the
built-ins until the catalog lands so a cold render is unchanged.
Built-in names always resolve through i18n, never through the catalog's
server-seeded English name — otherwise every non-English workspace would
see 'In Progress' where it used to see '进行中'. Custom names come from
the catalog. That rule lives in one place: useStatusLabel.
Applied it to every surface that shows an issue's OWN status (detail
activity feed, table group headers and CSV export, hover card, inbox),
which previously showed the category label and so could not tell two
statuses in the same category apart.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
* feat(views): show custom statuses on cards and in filters
Board columns and list sections are categories, so two issues in the
same column can sit on different statuses with nothing to tell them
apart. CustomStatusChip is that signal, and it renders nothing for a
built-in — a workspace with no custom statuses looks unchanged.
The status filter now lists the workspace catalog instead of the 7
hardcoded built-ins. Without this an issue moved onto a custom status
could not be narrowed to at all. Picker and filter share one
useStatusOptions hook so the two lists cannot drift apart.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
* feat(settings): issue status catalog management with drag reorder
Owner/admin CRUD for custom statuses, organised by CATEGORY rather than
as one flat list. Category is not decoration here — it is the behavior a
status inherits, and picking it is the one moment an admin can, since it
is immutable afterwards. Built-ins are shown but locked: each is its
category's canonical definition.
Drag reorder writes intra-category positions starting at 1, because the
built-in is seeded at 0 and PATCH on it is a 403, so it always heads its
category. The optimistic cache write re-SORTS rather than only writing
positions, or the drag would visually undo itself until the refetch.
custom_issue_statuses is now published to the frontend, so the tab can
hide creation when the deployment has the rollout gate off instead of
offering a button that 403s.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
* fix(issues): close the category contract end to end
Elon's review found the board only half-converted: the UI targeted
categories while the server still paged by concrete status key, so a
card on a custom status was dropped twice — it never came back from the
server, and the row filter would have discarded it if it had.
1. New `status_category` group kind (and compound secondary axis).
Grouping rewrites the column with a static CASE built from the
catalog; paging expands the category to concrete keys and stays on
`status = ANY(...)`, so (workspace_id, status) is still usable. A
workspace with no custom statuses gets exactly `i.status` back.
Row filter, facet totals and swimlane cells all fold by category now.
2. Column visibility split from the status filter. Hiding a column used
to write the other six built-in keys into statusFilters, so hiding
Backlog silently dropped every CUSTOM status in other categories.
Display state is now its own field. The saved-view baseline also
stopped deleting custom status filters on reopen.
3. The board/list right-click menu reads the shared catalog options, so
every entry point that can set a status offers the same set.
4. Remaining presentation consumers resolve a category instead of
guessing: gantt bars were keyed by IssueStatus and went colorless on
any custom key; hover card, sidebar, search, tab strip, inbox row,
create toast and the activity feed all resolve explicitly now.
5. Reorder is one atomic statement behind a new endpoint, and the client
submits ACTIVE rows only. The per-row PATCH loop half-committed when
an archived row rejected mid-sequence.
Also: one catalog observer per card instead of three, and the stale
tasks/todo.md draft is gone.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
* fix(issues): atomic reorder, category behavior sweep, rollout-safe grouping
Second review round.
1. Reorder is now one transaction holding the catalog's SHARED lock. The
old shape validated ids, THEN ran the UPDATE — an archive committing
in that window left the UPDATE silently skipping the archived row
while the earlier rows were rewritten, and still returned 200. The
authoritative active set is read under the lock and the payload must
match it EXACTLY: a partial order would assign positions from the
array index and collide with the rows it left out. Affected rows are
checked against the payload before commit.
2. Swept the Web behavior consumers that still compared raw keys, so a
custom category actually inherits behavior: gantt hide-completed,
batch backlog parking, done/cancelled strikethrough, the mark-done
button, sub-issue progress, search cancelled-demotion and mention
demotion/dimming. issueBehavesAs is the one predicate they share, and
it fails toward showing more and asking more. Two glyph sites (gantt
row, swimlane lane) were still painting custom in_review as Todo.
3. Rollout safety. status_category is a contract this PR introduces, and
the default-off creation flag does not guard the request path, so a
new Web build would have 400'd against any un-upgraded pod — for every
workspace. The client now sends it only once the catalog confirms the
workspace HAS a custom status, which can only be true if the fleet
already serves this version. Everyone else keeps the exact request
they made before.
That also uncovered a real defect: plain status grouping 500'd on a
workspace holding custom statuses, because the descriptor rejected any
non-built-in value and failed the whole response.
4. One catalog read per resolve instead of three, and the common path
reads none at all. Pinned with observable scan-count tests rather than
an assertion that the SQL string matches.
5. A custom-key filter no longer routes on a guess. categoryOf answers
todo for anything unloaded, so a saved qa filter paged the todo column
while the query still restricted status=qa — empty on every cold load,
permanent if the catalog fetch failed. Unresolved custom keys now
contribute no column until the catalog is authoritative; facet counts
drop unknown keys rather than inflating todo.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
* fix(issues): swimlane category axis, catalog tri-state, tests with teeth
Third review round.
1. Swimlane was only half-converted. The compound spec sent
secondary=status_category unconditionally — the same rolling-deploy
hole the board/list branches had — and the client row matcher then
compared the raw key, so a qa row the server correctly folded into the
in_review cell was thrown away again. Both now follow the same
hasCustomStatuses switch, and the matcher knows which axis it is on.
2. The cold-load fix did not actually make the surface wait. Returning an
empty column set is indistinguishable from "narrow to nothing", so the
surface fetched nothing and rendered an empty board with no spinner;
on catalog failure that was permanent, with no retry. Resolution is
now pending / resolved / error: pending and error suspend fetching AND
hold loading, error surfaces isStatusCatalogError. Fetching is
suspended rather than left to fall back to all columns, which would
briefly show the UNFILTERED board to someone opening a saved qa view.
The group contract also joins the branch cursor identity, so a status
cursor cannot ride into a status_category request.
3. Two tests were false-green and are replaced:
- The reorder race archived BEFORE calling reorder, so the active-set
check caught it even with the lock deleted. It now reproduces the
real interleaving (reorder parked on the lock, archive commits, then
reorder proceeds) and fails without the shared lock — with a 200,
which is the actual hole.
- The catalog read count read pg_stat_user_tables, whose counters flush
asynchronously, and asserted delta <= 1 — vacuous at zero. It now
counts through an injected querier and asserts exactly 1 read and 0
category-expansion reads.
Also: plain status grouping 500'd on a workspace holding custom statuses
(the descriptor rejected non-built-in values and failed the whole
response) — that is the path the Table view and every client fallback
use. And the chat mention items dropped status_category, so a custom done
status in Current/Recent rendered as an active Todo.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
* fix(issues): give the status catalog failure a real user path
Fourth review round. One group, both halves of it.
A failed status catalog suspends row fetching, because a custom status
filter cannot be routed to a column without it. That left isLoading and
isEmpty both false, so IssueSurface fell straight through to the content
branch and rendered an empty view with no explanation and no way out —
the controller's isStatusCatalogError had no consumer.
The catalog now carries its own `retry` (React Query's refetch) rather
than just a failure flag, the controller re-exposes it, and IssueSurface
renders an explicit error state ahead of loading/empty/content. Component
test covers the full loop: catalog rejects, error appears with no row
requests issued, clicking retry succeeds, fetching resumes.
The blocking condition is also narrowed. A BACKGROUND refetch can fail
while the last successful catalog is still cached, and error-before-loaded
meant that perfectly serviceable snapshot got a retry screen in front of
it. Only a failure with no data behind it blocks now; otherwise the last
successful catalog keeps resolving.
That rule lives in status-behavior.test.ts, where it can be asserted
directly. The component test asserts the wiring — it was originally
written to claim the rule too, but reverting the narrowing did not fail
it, so the claim moved to the layer that actually holds it.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
* fix(issues): accept custom status keys in table filters.statuses
Filtering a board, list or table by a custom status 400'd the whole
request: `filters.statuses` carries the exact keys the user picked, and
the table query still validated that list against the 7 built-ins. Every
surface errored out instead of narrowing — the one thing the filter work
in this PR exists to do.
The predicate is a parameterized `status = ANY(...)`, so an unknown key
simply matches nothing; the check is now non-empty and length-bounded.
Found while writing the post-deploy verification steps, which is where
the gap became obvious: "filter by QA" was step one and it 400s.
MUL-6243
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
The /download page rendered every Windows and Linux button as disabled.
v0.4.28's release run had its Windows desktop packaging job fail and its
Linux job never finish, so the release carried Mac builds only — and the
page reads whatever the newest release happens to be.
Release selection guarded against this with a time proxy: fall back to
the previous release for the first hour after publish, on the assumption
that a gap means uploads are still in flight. That only covers the
mid-flight case. A release whose packaging job failed outright never
completes, so once the hour elapsed the page pinned itself to a release
it could not fully serve.
Select on the actual signal instead: parse each of the recent releases
and show the newest whose desktop asset set is complete, so every button
on the page resolves to a real file. Widen the candidate window from 2 to
5 and log when the newest release is skipped, since a release that never
completes is otherwise invisible until someone reports dead buttons.
Completeness now has one definition, `hasCompleteAssetSet`, shared with
the "view all releases" fallback link that had counted assets inline.
Verified against live GitHub data: v0.4.28 is skipped and v0.4.27 serves
all twelve artifacts.
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Covers the 7 PRs merged onto main after the v0.4.27 tag: member actor /
multi_actor custom property types (#7075), bulk skill update from source
(#6930), the desktop changelog link on the update prompt (#7088), and the
same-workspace tab swap fix (#7092).
Bumps apps/web to 0.4.28. Patch bump: per-workspace custom issue statuses
(#7065) is the largest change in the window but ships behind the default-off
custom_issue_statuses rollout flag, so nothing user-facing today is
announcement-grade.
Test-infrastructure work (#7081, #7072) is intentionally absent from the
changelog: it changes no user-visible behavior.
Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
MUL-6291. Vitest's own breakdown for `@multica/views` showed the shape of the
problem: environment 633s and import 644s against only 508s of actual test
execution. Two thirds of a cold frontend-test run went on building a jsdom per
file and importing its module graph, not on running assertions.
- 94 pure-logic `.test.ts` files across views/web/desktop now declare
`// @vitest-environment node`. Measured on the 45 converted views files:
environment 53.15s -> 4ms, wall 8.72s -> 2.03s. Candidates were picked by
module-graph analysis (skipping type-only imports) and rejected when the code
under test branches on `typeof window`/`document`, where node would silently
take the SSR path and still pass; the remainder were verified empirically and
reverted where they failed.
- The three jsdom setup files bail out when there is no DOM, so a node-env suite
can share them.
- schedule-editor: the pure state transitions (isFullDay, displayWindow,
timeAnchorOf, defaultAtTime, defaultEveryTime, clampWindow, toggleDay) move
out of the component into transitions.ts, with the full combination matrix as
a table-driven node-env suite. 34 cases in 3ms, replacing four DOM mounts.
- Cross-layer duplicates removed where a canonical layer already owns the
matrix: originSourceUrl (skills/lib/origin.test.ts) was re-run through the
refresh dialog and the detail page; needsSourceBackfill
(core/onboarding/needs-backfill.test.ts) through the backfill modal. Each
component keeps one wiring case naming the canonical file.
No behaviour change: the transitions extraction is a pure move, and every
removal names where the same contract is still covered.
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>