Extract shared isCherryManagedModel / findCherryProviderKey helpers and route CHERRY_-prefixed env filtering through the existing omitKeysByPrefix, replacing duplicated predicates across builders/clear/parser/draftUpdater. Pure refactor; behavior unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
Add an inline `barrel` ESLint plugin (same pattern as the lifecycle plugin) implementing §6.4 rules 1-3: no-export-star; index-no-impl (a barrel is pure re-export — no default/local exports, no side-effect imports, no top-level logic); no-index-tsx (no exceptions — index routes use the flat dot form); named-only; closed (sole-entry boundary, judged against the outermost crossed barrel dir so a nested barrel's index cannot bypass its parent's door); no-nesting; no-bucket-root. Barrel discovery classifies pure re-export index.ts files once at config load; import resolution covers relative specs plus @renderer/@main/@shared/@data, with deliberately-unresolved single-file aliases documented inline. All rules start at warn and tests are exempt by design; a full-src sweep reports 0 errors, with warnings matching the audited migration backlog.
src/main/index.ts → main.ts and src/preload/index.ts → preload.ts, so the process entries stop occupying the barrel-reserved index filename (§6.4) without needing an exemption list. The rename is wired through electron.vite (main build.lib.entry — rollupOptions.input would bypass electron-vite's output-format detection — and the preload input key), package.json main (out/main/main.js), WindowManager's default preload filename, MainWindowService's webview preload path, and DbService's slow-query stack filter, which matches the bundled artifact name. preload.d.ts becomes globals.d.ts: sharing preload.ts's basename makes TypeScript drop the .d.ts as that file's presumed output declaration, and tsgo only applies its global Window augmentation when the declaration sorts before its importer, so the file keeps a distinct, alphabetically-early name. Entry-path mentions in code comments and reference docs follow.
Rename the four directory-form index routes (settings/, settings/mcp/, app/mini-app/, app/paintings/) to TanStack's default-supported flat dot form (settings.index.tsx, …) so no file under routes/ is named index.* and the §6.4 iron rule — the index filename is reserved for barrels — holds across src with no exceptions. URLs and route ids are unchanged; routeTree.gen.ts regenerates with only the four import paths moved. Naming §6.4 drops the routes/ carve-out, §6.6 documents the dot-form token, and the routes READMEs replace a stale bare-index example.
CLI_TOOLS.label was a hardcoded English string rendered directly in the
sidebar. Store an i18n key instead and resolve it with t() at the point
of use, matching the existing convention for provider display names.
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
Merges the inject.ts and draft.ts write pipelines (removing the circular
fallback between them), dedupes small helper functions repeated across
builders/clear/parser/values, unifies sanitizeProviderName between the
renderer and main process to remove a cross-process drift risk, and
consolidates the OpenCode npm mapping and per-tool baseUrl resolution
into single-source-of-truth tables in resolvers.ts.
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
Promote §6.4 from three loose rules to the full barrel contract (re-export-only, enforced-sole-entry-or-no-barrel, no nesting, one-cohesive-unit) and point shared §3.1 / main §2.1 / renderer §5 at it, fixing main's stale pointer to shared. Reserve the index filename for barrels — always index.ts, never .tsx outside routes/ — with implementations in named files; add the renderer component-directory form and align §4.2's component test. Note what the contract does not remove: dev builds load a barrel's full graph (rule 4 is the bound), and dynamic import() crosses a boundary through the barrel, with the React.lazy form in renderer §5. Docs only; lint enforcement and call-site fixes follow.
The three 8000ms deadlines on positive-event waits in watcher.test.ts were the
tightest in the file. On loaded GitHub CI Linux runners, native inotify delivery
can lag several seconds behind the write, so the maxDepth test's add wait for
root.txt intermittently timed out at 8000ms — while the sibling add test on the
15000ms default passes on the same runner.
Drop the 8000ms overrides so these waits inherit waitForEvent's 15000ms default
(still safely under vitest's 20000ms testTimeout). This is a deadline ceiling,
not a delay: passing runs resolve the instant the event arrives, so happy-path
speed is unchanged. depth:0 provably arms the inotify watch on the root dir
(chokidar handler _handleDir runs at depth 0, 0 <= 0), so root.txt's missing add
is CI event latency, not a logic bug.
SearchPopup is statically imported by four shell modules (AppShell,
ShellTabBarActions, AgentChatNavbar, MessagesService), and its panel body
was the sole static bridge from the main window shell to the chat message
renderer and the resource-edit dialog family — dragging ~1.4 MB and its
transitive graph into the main window's first-screen modulepreload set.
- Defer GlobalSearchPanel via React.lazy + Suspense inside SearchPopup so
the imperative shell stays static (show()/hide() unchanged) and the heavy
panel loads on first open. Main first-screen static JS: 13.17 MB -> 5.70 MB.
- Remove the dead locateToMessage() from MessagesService (zero callers) and
its top-level SearchPopup import, dropping a service->UI layering
inversion. quickAssistant/selection no longer pull the search panel graph
(13.69 -> 13.11 MB / 13.71 -> 13.12 MB).
- add an auto-discovered import-x/no-restricted-paths guard so each services/<topic>/
is reachable only through its index.ts barrel (blocks L/P/B, renderer-architecture.md §3.1/§5)
- StreamDispatchService: normalize the module-level listener registry into a class singleton
- copy.ts: drop the Service suffix from the stateless clipboard helper (stays in services/ for the side effect)
- move stateless reads to utils/: skillSearch.ts and model/resolve.ts (reads never promote)
- ImportService: fix the direct-import singleton form (class ImportService + importService instance)
Implements the accepted upstream items from issue 16738:
- JobSettledEvent<TPayload> now carries typed input, parentId, and the
final metadata (post-patchMetadata); JobContext gains parentId. Both
are projections of the persisted snapshot - no extra DB reads.
- JobListFilter.type widened to string | string[] (inArray), parentId
filter added (rides job_parent_id_idx); ACTIVE_JOB_STATUSES exported
from shared schemas replacing JobService's private copy; list/count
WHERE composition extracted into a shared listConditions helper.
- JobManager.enqueueTx(tx, ...) rides the caller's withWriteTx
transaction so business writes and the job INSERT commit atomically;
post-commit side effects are deferred one microtask and re-read the
row (a rollback discards the resolver). JobService gains createTx /
findActiveByIdempotencyKeyTx; enqueue is refactored onto the shared
prepareEnqueue with unchanged external behavior.
Refs #16738
Move the per-key serialisation primitive out of ai/streamManager so the
concurrency README's recommendation matches its location. Widen
runExclusive to accept sync tasks (() => T | Promise<T>), generalize the
class doc with an extension guideline, and add a sync-task ordering test.
AiStreamManager keeps its behavior via the updated import.
Replace the fuzzy business-orchestration routing criterion with an outward-side-effects test: stateless shared modules default to utils/ and promote to services/ only for a stated reason (side effects, or a forced services import).
Rekey the Service/Manager suffix from stateful classes to stateful singleton capabilities, mechanism-independent: module-scope mutable state must take the class + singleton form, multi-instance helper classes take no suffix, and what counts as state is defined.
Codify renderer services/<topic>/ topic directories (single barrel entry, private topic-specific satellites exempt from shape routing, no UI) mirroring the existing main-process rule, and add the middle growth step to Naming 4.10.
Align residual wording across the three docs: same-kind peering edges in the renderer dependency table, the section-6 corollary vs its own worked example, quick-reference suffix note, utils charter and examples.
Rename src/renderer/components/Icons -> icons and Tags -> tags (bucket
directories, lowercase plural per naming-conventions §4.3) and
RichEditor/toolbar.tsx -> Toolbar.tsx (component file, PascalCase per §4.7)
so the on-disk casing matches the existing imports. No import statements
changed.
On macOS's case-insensitive filesystem the mismatch resolved fine at build
time, but oxlint-tsgolint builds a case-sensitive TS program: files walked
under their real disk case failed to match program entries keyed under the
imported case, crashing `oxlint --fix` (and typecheck with TS1149) with
"Unmatched files ... Expected file X to be in program tsconfig.web.json".
This aborted the whole `pnpm lint` chain before typecheck/i18n/format ran.
Re-sync the "Target vs Current State" section with the code after the
dependency-cleanup landed. §8 now tracks only what is still unfinished:
- Drop the "Already aligned" list and the six resolved deviations
(config/, queue/, components/app/Navbar, the utils/ root barrel, and
the "Boundary enforcement: none" row) -- once resolved they no longer
violate the target, so they need not appear.
- Correct the components/app/Sidebar row: it is not dead code. It is
rendered by components/layout/AppShell.tsx (main-window shell), so it
folds into the App-shell row and moves to windows/main with that shell.
- Sync the enforcement status in §5 and §8: the import/no-restricted-paths
gate is live -- shared-layer edges at error, pages -> pages at warn -- and
the remaining pages -> pages coupling is ~13, not ~35.
- Keep only the still-open half of the barrel item: utils/message/ still
needs a curated index.ts.
Add a maintenance note so resolved deviations are dropped, not recorded
as done -- the "already aligned" list is what rots.
Two clusters of parallel per-tool tables that had to be kept in sync by hand
are collapsed to one source each:
- Main: getPackageName / getToolInstallSpec / getCliExecutableName were three
independent switches with no compiler link — the exact structure that let the
Kimi version-lookup package ('kimi-code') drift from the installed package
('@moonshot-ai/kimi-code'). They now derive from one CODE_CLI_PACKAGE_SPECS
row per tool (executable + packageName + install), so the two package names
agree by construction. The existing regression test pins the invariant.
- Renderer cliConfig: FILE_CONFIGURED_CLI_TOOLS and getCliConfigTargets were two
lists of the same six file-based tools. Both now derive from one
CLI_CONFIG_TARGETS map; the set is just its keys.
Behavior-preserving: every value is identical to the switch it replaced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
The 21 test-fixture eslint warnings were pre-existing debt unrelated to
the dependency-cleanup refactor. Clear them in the test mocks:
- Add a default type="button" to mock Button/MenuItem so the fakes match
the real @cherrystudio/ui default (no-missing-button-type).
- Rename the local PopoverCtx to PopoverContext in the two translate
mocks (naming-convention/context-name).
- Suppress no-clone-element where a mock faithfully reproduces Radix
asChild slot merging via cloneElement -- the correct primitive there.
Renderer eslint drops from 65 to 44 warnings (0 errors). The remaining 44
are production debt (exhaustive-deps, i18n templates) and the Block P
sibling-page gate, both left for dedicated follow-ups.
Backfill tests for the untested cliConfig read/clear/update paths ahead of
merging the scattered per-tool switches into a single descriptor:
- parser.test.ts: round-trips baseUrl/apiKey/model and managed config blobs
through readCliConfigDraft → extract* for all six tools, plus malformed→null
- draftUpdater.test.ts: formatCliConfigDraftFile JSON pretty-print + toml
passthrough; updateCliConfigDraftConfig applies/clears blobs while preserving
the connection
- clear.test.ts: clearCliConfig strips managed keys/providers/auth for
claude/codex/opencode and is a no-op for provider-less tools
Locks the current on-disk behavior so the descriptor refactor is verifiable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
- hide the vertical scrollbar and tighten vertical padding (py-5 -> py-2.5)
on the CLI tool detail content area
- use bg-background for the version status card surface
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
getPackageName returned the unscoped 'kimi-code' for Kimi, while the tool
is actually installed as '@moonshot-ai/kimi-code' (getToolInstallSpec). The
version-registry lookup therefore queried an unrelated npm package, so
getVersionInfo() would report the wrong latest version and leave needsUpdate
stuck — currently dormant (its only consumer is the never-enabled
autoUpdateToLatest branch), but a latent landmine the moment getVersionInfo
is wired into the version UI.
Align getPackageName with the install spec and add a regression test that
pins, for every npm-installed tool, that the version-lookup package name
equals getToolInstallSpec's package.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
- share Field/updateSectionField/updateField via ConfigFieldPrimitives;
drop the per-tool re-declarations in Codex/OpenCode/Gemini/Qwen/Kimi
- collapse CHERRY_PREFIX into CHERRY_PROVIDER_PREFIX (same literal)
- derive CLI_ICONS from CLI_TOOLS so the icon map is declared once
- applyManagedTomlSettings now delegates to applyManagedJsonSettings
via an optional topLevelKeys param (drops the duplicated section loop)
- wrap the openclaw result routes in a shared asOperationResult helper
- extract runInstallTool from the identical install/upgrade bodies
- parallelize the independent provider/api-key/model DataApi reads in
inject/draft; resolve the two Gemini config paths once each
Behavior-preserving cleanup only; renderer + main tests, typecheck and
lint all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: eeee0717 <chentao020717Work@outlook.com>
Follow-ups after the type-bucket cleanup:
- Promote the layer-edge boundary gate to error. Block L (shared buckets:
utils purity + the shared->app reverse edge) is at zero violations, so
RENDERER_BOUNDARY is now 'error' — regressions fail lint. Block P
(sibling pages) stays warn until features-ization (13 known warnings).
- Remove dead code: utils/translate/translateInputText had no production
callers, only a barrel re-export.
- Fix a case-only vi.mock path in app/Sidebar.test that resolved on
macOS's case-insensitive FS but would miss on Linux CI:
'../../icons/SVGIcon' -> '../../icons/SvgIcon'.
Enforce the renderer type-bucket layering (renderer-architecture.md §5)
with import-x/no-restricted-paths, resolving @renderer/@shared/@logger
aliases via eslint-import-resolver-typescript.
Two env-gated rule instances over non-overlapping file scopes:
- block L (shared buckets: components/hooks/services/utils) guards the
reverse layer edge and utils purity — gated by RENDERER_BOUNDARY_ERROR.
- block P (pages) guards page->window and sibling-page coupling — gated
by RENDERER_PAGE_SIBLING_ERROR.
Both default to 'warn' so CI stays green while violations are burned
down, each independently promotable to 'error'. Add eslint-plugin-import-x
+ eslint-import-resolver-typescript and allow the native unrs-resolver
build in pnpm-workspace.
Reorganize src/renderer so module placement and naming follow
renderer-architecture.md (type x domain) and naming-conventions.md §5.2.
Relocate modules to the correct bucket by role:
- utils/oauth and chat/resources/resourceListRevealEvents -> services/;
extract knowledge orchestration into services/knowledgeContent.ts.
- chat message filePath and the stateless hooks/agent/* helpers -> utils/;
utils/model/utils -> utils/model/capabilities; utils/editorUtils ->
utils/editor (+ extracted icons/EditorIcon).
- useMcpServerTrust -> its sole consumer under pages/settings/McpSettings/.
Normalize file/directory naming to the convention:
- Icons/ -> icons/, Tags/ -> tags/; PascalCase the component files in
CodeBlockView, CodeToolbar, VirtualList, QuickPanel, RichEditor,
app/Navbar, Buttons/ActionIconButton, LanTransferPopup;
translate/_mutationFeedback -> useMutationFeedback.
Sink structural pieces out of window entries:
- selection action components -> components/selection/ (+ SelectionToolbarView);
extract the window-frame hook into hooks/useWindowFrame.ts.
Update all import sites; drop orphaned v1 dead code (DraggableList/list.tsx,
Buttons/index.ts).
Record the placement rule settled during the renderer cleanup: utils/ is
stateless and domain-agnostic and MAY import downward infra
(data/ipc/workers), while services/ owns state/lifecycle or performs
business/domain orchestration. A helper touching infra is still a helper;
the decisive test is role, not purity.
- renderer-architecture.md: co-equal utils/data/ipc/workers foundation
(§2); positive service identity and the revised utils row (§3).
- naming-conventions.md §5.2: add the stateless-orchestration -> services
routing row; note utils may call downward infra.
- main-process-architecture.md: Pure -> Stateless; route by role.