feat(desktop): add input context and inline comments#18
Conversation
There was a problem hiding this comment.
Findings
-
[Blocker] Prompt-context enrichment now fails silently when a selected attachment cannot be read or when the reference URL fetch fails, so
generate/apply-commentproceed with partial grounding and no user-visible error. That violates the repo's "no silent fallbacks" constraint and can produce a design that looks grounded when the grounding step never succeeded, evidenceapps/desktop/src/main/prompt-context.ts:55,apps/desktop/src/main/prompt-context.ts:104,apps/desktop/src/main/index.ts:126,apps/desktop/src/main/index.ts:186
Suggested fix:import { CodesignError } from '@open-codesign/shared'; } catch (error) { throw new CodesignError( `Failed to read attachment "${file.path}"`, 'ATTACHMENT_READ_FAILED', { cause: error }, ); } if (!response.ok) { throw new CodesignError( `Reference URL fetch failed (${response.status}) for ${url}`, 'REFERENCE_URL_FETCH_FAILED', ); }
-
[Major] The new persisted
designSystempayload is written intoconfig.tomlwithout any nested schema/version field or migration boundary. The docs explicitly require schema-versioning for on-disk data, and future extractor changes will have no reliable way to distinguish old snapshots from new ones, evidencepackages/shared/src/config.ts:30,packages/shared/src/config.ts:50,apps/desktop/src/main/onboarding-ipc.ts:111
Suggested fix:export const StoredDesignSystem = z.object({ schemaVersion: z.literal(1), rootPath: z.string().min(1), summary: z.string().min(1), extractedAt: z.string().min(1), // ... }); const next: Config = { ...cfg, designSystem: designSystem ? { schemaVersion: 1, ...designSystem } : undefined, };
-
[Major]
inspectReferenceUrl()reads the full response body into the Electron main process before applying any size or content-type guard, so a large or non-HTML URL can stall the app and inflate prompt size even though only 1,200 characters are eventually kept, evidenceapps/desktop/src/main/prompt-context.ts:88
Suggested fix:const contentType = response.headers.get('content-type') ?? ''; const contentLength = Number(response.headers.get('content-length') ?? 0); if (!response.ok || !contentType.includes('text/html') || contentLength > 256_000) { throw new CodesignError( `Unsupported reference URL response for ${url}`, 'REFERENCE_URL_UNSUPPORTED', ); }
Summary
- Review mode: initial. Three issues above block this from being ready: silent fallbacks in prompt-context ingestion, an unversioned on-disk
designSystemsnapshot, and unbounded reference-URL reads in the main process. Separately, the GitHub commit metadata forebb7e2e7b6fe70f23ec517920195d1ce8dd095cbdoes not show aSigned-off-by:trailer, so the DCO requirement still appears unmet.
Testing
- Not run (automation):
corepack pnpm --filter @open-codesign/core test,corepack pnpm --filter @open-codesign/desktop typecheck, andcorepack pnpm --filter @open-codesign/shared typecheckcould not run in this runner becausenode_modulesare absent; the environment also reports Nodev20.20.2while the repo requires Node 22+.
open-codesign Bot
| ...(description ? { description } : {}), | ||
| excerpt: cleanText(stripHtml(html), MAX_URL_EXCERPT_CHARS), | ||
| }; | ||
| } catch { |
There was a problem hiding this comment.
This turns a failed reference-URL enrichment step into a silent fallback. The generate/apply-comment IPC handlers both rely on preparePromptContext(), so returning only { url } here means the request keeps going with partial grounding and no user-visible error, which violates PRINCIPLES §10.
Suggested fix:
import { CodesignError } from '@open-codesign/shared';
if (!response.ok) {
throw new CodesignError(
`Reference URL fetch failed (${response.status}) for ${url}`,
'REFERENCE_URL_FETCH_FAILED',
);
}
} catch (error) {
throw new CodesignError(
`Failed to inspect reference URL ${url}`,
'REFERENCE_URL_FETCH_FAILED',
{ cause: error },
);
}| }); | ||
| export type BaseUrlRef = z.infer<typeof BaseUrlRef>; | ||
|
|
||
| export const StoredDesignSystem = z.object({ |
There was a problem hiding this comment.
designSystem is a new on-disk payload, but this schema has no nested version marker. The repo docs call out schema-versioning for persisted data explicitly; without it, any future extractor shape change becomes a blind breaking change for existing config.toml files.
Suggested fix:
export const StoredDesignSystem = z.object({
schemaVersion: z.literal(1),
rootPath: z.string().min(1),
summary: z.string().min(1),
extractedAt: z.string().min(1),
// ...
});| const controller = new AbortController(); | ||
| const timer = setTimeout(() => controller.abort(), 4_000); | ||
| try { | ||
| const response = await fetch(url, { |
There was a problem hiding this comment.
This fetch path pulls the entire response into the Electron main process with response.text() before any content-type or size guard. For an arbitrary user-entered URL, that is enough to freeze the UI on large pages/binary responses even though the code only keeps 1,200 characters afterward.
Suggested fix:
const contentType = response.headers.get('content-type') ?? '';
const contentLength = Number(response.headers.get('content-length') ?? 0);
if (!response.ok || !contentType.includes('text/html') || contentLength > 256_000) {
throw new CodesignError(
`Unsupported reference URL response for ${url}`,
'REFERENCE_URL_UNSUPPORTED',
);
}ebb7e2e to
27e65e7
Compare
There was a problem hiding this comment.
Findings
-
[Major] Attachment grounding still reads the entire file into the Electron main process before truncating it to a 6,000-character excerpt, so a user-selected large file or binary can stall the app or spike memory even though only a tiny prefix is ever used, evidence
apps/desktop/src/main/prompt-context.ts:58,apps/desktop/src/main/prompt-context.ts:76
Suggested fix:import { open } from 'node:fs/promises'; const MAX_ATTACHMENT_BYTES = 256_000; if (file.size > MAX_ATTACHMENT_BYTES) { throw new CodesignError( `Attachment "${file.name}" is too large (${file.size} bytes).`, 'ATTACHMENT_TOO_LARGE', ); } const handle = await open(file.path, 'r'); const buffer = Buffer.alloc(Math.min(file.size, MAX_ATTACHMENT_BYTES)); const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0); await handle.close();
-
[Major] The new prompt-grounding path leaks absolute local filesystem paths to the model provider: attachment prompts include
file.path, and the scanned design-system summary includes the absolute repo root. That unnecessarily exposes usernames/project names over the network and is not needed for generation, evidencepackages/core/src/index.ts:97,packages/core/src/index.ts:115,apps/desktop/src/main/design-system.ts:166
Suggested fix:import { basename } from 'node:path'; const repoLabel = basename(designSystem.rootPath); const lines = ['## Design system to follow', `Repository: ${repoLabel}`]; const attachmentLines = [`${index + 1}. ${file.name}`];
-
[Major]
PreviewPanenow trusts anywindow.postMessagepayload with the right shape, but the generated preview runs untrusted JavaScript undersandbox="allow-scripts"and can forge those messages itself. That lets artifact code spoof element-selection and iframe-error events in the parent UI, evidenceapps/desktop/src/renderer/src/components/PreviewPane.tsx:26,apps/desktop/src/renderer/src/components/PreviewPane.tsx:72
Suggested fix:const iframeRef = useRef<HTMLIFrameElement>(null); function onMessage(event: MessageEvent): void { if (event.source !== iframeRef.current?.contentWindow) return; if (isOverlayMessage(event.data)) { // ... } } <iframe ref={iframeRef} ... />
Summary
- Review mode: follow-up after new commits. Three issues remain in the latest head: full attachment reads in the main process, absolute local-path leakage into provider prompts, and untrusted preview
postMessagepayloads being accepted by the renderer. I did not find new direct provider SDK imports or dependency/license changes in this diff.
Testing
- Not run (automation): the runner is on Node
v20.20.2, the repo requires Node 22+,pnpmis not on PATH,node_modulesare absent, andcorepack pnpm vitest run apps/desktop/src/main/prompt-context.test.ts packages/core/src/generate.test.ts packages/shared/src/config.test.tsfailed withCommand "vitest" not found. - Missing coverage: not found in the touched tests for oversized-attachment handling or renderer-side
postMessagesource validation around inline comments / iframe errors.
open-codesign Bot
| const extension = extname(file.name).toLowerCase(); | ||
| let buffer: Buffer; | ||
| try { | ||
| buffer = await readFile(file.path); |
There was a problem hiding this comment.
This still reads the full attachment into memory before the code decides whether it is text and before it trims the excerpt to 6,000 characters. A large file or binary will block the Electron main process for no gain.
Suggested fix:
import { open } from 'node:fs/promises';
const MAX_ATTACHMENT_BYTES = 256_000;
if (file.size > MAX_ATTACHMENT_BYTES) {
throw new CodesignError(
`Attachment "${file.name}" is too large (${file.size} bytes).`,
'ATTACHMENT_TOO_LARGE',
);
}
const handle = await open(file.path, 'r');
const buffer = Buffer.alloc(Math.min(file.size, MAX_ATTACHMENT_BYTES));
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
await handle.close();| if (attachments.length === 0) return null; | ||
| const body = attachments | ||
| .map((file, index) => { | ||
| const lines = [`${index + 1}. ${file.name} (${file.path})`]; |
There was a problem hiding this comment.
This prompt section sends absolute local paths upstream (file.path here, plus the scanned repo root in the design-system summary). That leaks usernames and project names to the provider even though the model only needs file names / relative repo labels.
Suggested fix:
import { basename } from 'node:path';
const repoLabel = basename(designSystem.rootPath);
const lines = ['## Design system to follow', `Repository: ${repoLabel}`];
const attachmentLines = [`${index + 1}. ${file.name}`];|
|
||
| useEffect(() => { | ||
| function onMessage(event: MessageEvent): void { | ||
| if (isOverlayMessage(event.data)) { |
There was a problem hiding this comment.
This trusts any window.postMessage payload with the expected shape. Because the preview iframe runs untrusted generated JavaScript under sandbox="allow-scripts", the artifact can forge ELEMENT_SELECTED / IFRAME_ERROR messages and drive parent-side UI state without a real user click.
Suggested fix:
const iframeRef = useRef<HTMLIFrameElement>(null);
function onMessage(event: MessageEvent): void {
if (event.source !== iframeRef.current?.contentWindow) return;
if (isOverlayMessage(event.data)) {
// ...
}
}
<iframe ref={iframeRef} ... />Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com>
27e65e7 to
7b2797a
Compare
There was a problem hiding this comment.
Findings
-
[Major] IPC contract changes are still unversioned —
GeneratePayloadgained new fields and three new IPC channels were added without av1namespace orschemaVersion, which violates the repo's compatibility/upgradeability rule and makes the next payload/channel change a breaking swap instead of a parallel rollout, evidencepackages/shared/src/index.ts:106,packages/shared/src/index.ts:116,apps/desktop/src/preload/index.ts:38,apps/desktop/src/main/index.ts:70
Suggested fix:export const GeneratePayloadV1 = z.object({ schemaVersion: z.literal(1), prompt: z.string().min(1).max(32_000), history: z.array(ChatMessage).max(200), model: ModelRef, baseUrl: z.string().url().optional(), referenceUrl: z.string().url().optional(), attachments: z.array(LocalInputFile).max(12).default([]), }); ipcMain.handle('codesign:v1:generate', async (_e, raw) => { const payload = GeneratePayloadV1.parse(raw); // ... });
-
[Major] Design-system scanning still masks partial failures and does full-file reads in the Electron main process — unreadable directories/files are silently skipped, but the flow still logs
designSystem.scan.okand the renderer shows “Design system linked”, while each selected file is loaded completely even though only the first 32,000 chars are used. Large repos can stall the main process and incomplete scans are surfaced as success, evidenceapps/desktop/src/main/design-system.ts:89,apps/desktop/src/main/design-system.ts:204,apps/desktop/src/main/index.ts:105,apps/desktop/src/renderer/src/store.ts:215
Suggested fix:import { open } from 'node:fs/promises'; const failures: string[] = []; try { const handle = await open(file.fullPath, 'r'); const buffer = Buffer.alloc(MAX_FILE_CHARS); const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0); await handle.close(); const snippet = buffer.subarray(0, bytesRead).toString('utf8'); collectCssVarValues(snippet, colors, spacing, radius, shadows); collectLooseValues(snippet, colors, fonts, spacing, radius, shadows); } catch (error) { failures.push(`${file.relativePath}: ${error instanceof Error ? error.message : String(error)}`); } if (failures.length > 0) { throw new CodesignError( `Design system scan skipped ${failures.length} paths: ${failures.slice(0, 3).join('; ')}`, 'DESIGN_SYSTEM_SCAN_FAILED', ); }
-
[Minor] New renderer UI still hardcodes visual values instead of going through tokens/components — the new preview hint, iframe surface, send/apply buttons, and
kbdstyles add raw white/rgba values and inline font declarations, which violates the token-only constraint and will drift across themes, evidenceapps/desktop/src/renderer/src/components/PreviewPane.tsx:76,apps/desktop/src/renderer/src/components/PreviewPane.tsx:85,apps/desktop/src/renderer/src/components/InlineCommentComposer.tsx:74,apps/desktop/src/renderer/src/components/Sidebar.tsx:189,apps/desktop/src/renderer/src/components/Sidebar.tsx:198
Suggested fix:<div className="... bg-[var(--color-surface)] text-[var(--color-text-primary)] shadow-[var(--shadow-soft)]" /> <iframe className="... bg-[var(--color-surface)] border border-[var(--color-border)]" /> <Button className="...">Apply change</Button> <kbd className="font-mono ...">Enter</kbd>
Summary
- Review mode: follow-up after new commits. Three issues remain on the latest head: unversioned IPC/payload changes, design-system scanning that can silently succeed after partial failures while still full-reading candidate files in the main process, and new renderer literals that bypass the token system. I did not find dependency/license changes, direct provider SDK imports, or DCO problems in this diff.
Testing
- Not run (automation):
corepack pnpm vitest run apps/desktop/src/main/prompt-context.test.ts apps/desktop/src/renderer/src/components/PreviewPane.test.ts packages/core/src/generate.test.ts packages/shared/src/config.test.tsfailed because the runner is on Nodev20.20.2while the repo requires Node 22+,node_modulesare absent, andpnpmcould not findvitest. - Missing coverage: not found in the touched tests for
scanDesignSystempartial-failure/large-file handling or the new IPC contract surface aroundgenerate/applyComment/pick-*.
open-codesign Bot
| }); | ||
| export type SelectedElement = z.infer<typeof SelectedElement>; | ||
|
|
||
| export const GeneratePayload = z.object({ |
There was a problem hiding this comment.
This adds new IPC/payload surface area without versioning: GeneratePayload changed shape, ApplyCommentPayload is new, and the new codesign:* channels are still unversioned. That violates the compatibility/upgradeability rule in docs/PRINCIPLES.md and makes the next contract change a breaking swap instead of a parallel rollout.
Suggested fix:
export const GeneratePayloadV1 = z.object({
schemaVersion: z.literal(1),
prompt: z.string().min(1).max(32_000),
history: z.array(ChatMessage).max(200),
model: ModelRef,
baseUrl: z.string().url().optional(),
referenceUrl: z.string().url().optional(),
attachments: z.array(LocalInputFile).max(12).default([]),
});
ipcMain.handle('codesign:v1:generate', async (_e, raw) => {
const payload = GeneratePayloadV1.parse(raw);
// ...
});| for (const file of selected) { | ||
| let raw = ''; | ||
| try { | ||
| raw = await readFile(file.fullPath, 'utf8'); |
There was a problem hiding this comment.
The scan still swallows unreadable directories/files and then returns success, and it still readFile()s each selected candidate even though only MAX_FILE_CHARS are used. On a large repo that can both produce a misleading partial snapshot and stall the Electron main process.
Suggested fix:
import { open } from 'node:fs/promises';
const failures: string[] = [];
try {
const handle = await open(file.fullPath, 'r');
const buffer = Buffer.alloc(MAX_FILE_CHARS);
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
await handle.close();
const snippet = buffer.subarray(0, bytesRead).toString('utf8');
collectCssVarValues(snippet, colors, spacing, radius, shadows);
collectLooseValues(snippet, colors, fonts, spacing, radius, shadows);
} catch (error) {
failures.push(`${file.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
}
if (failures.length > 0) {
throw new CodesignError(
`Design system scan skipped ${failures.length} paths: ${failures.slice(0, 3).join('; ')}`,
'DESIGN_SYSTEM_SCAN_FAILED',
);
}| className="w-full h-full bg-white rounded-[var(--radius-2xl)] shadow-[var(--shadow-card)] border border-[var(--color-border)]" | ||
| /> | ||
| <div className="relative h-full"> | ||
| <div className="absolute left-5 top-5 z-10 rounded-full border border-[var(--color-border)] bg-[rgba(255,255,255,0.88)] px-3 py-1 text-[11px] text-[var(--color-text-secondary)] shadow-[var(--shadow-soft)] backdrop-blur"> |
There was a problem hiding this comment.
These new renderer additions still bypass the token system with raw white/rgba values and inline font declarations (bg-white, text-white, bg-[rgba(...)], style={{ fontFamily: ... }}). That will drift across themes and violates the repo's token-only UI rule.
Suggested fix:
<div className="... bg-[var(--color-surface)] text-[var(--color-text-primary)] shadow-[var(--shadow-soft)]" />
<iframe className="... bg-[var(--color-surface)] border border-[var(--color-border)]" />
<Button className="...">Apply change</Button>
<kbd className="font-mono ...">Enter</kbd>…in features Rebase onto origin/main exposed conflicts where PRs #18, #19, #20 had advanced store, preload, shared and main/index.ts beyond the branch point. Resolve by taking origin/main as the base and layering our cancellation additions on top: - shared/index.ts: add LocalInputFile, SelectedElement, ElementSelectionRect, ApplyCommentPayload, StoredDesignSystem exports; add generationId to GeneratePayload; keep CancelGenerationPayloadV1 - preload/index.ts: add locale API, pickInputFiles, pickDesignSystemDirectory, clearDesignSystem, applyComment; add generationId to generate payload; keep cancelGeneration with schemaVersion: 1 - main/index.ts: add applyComment, scanDesignSystem, preparePromptContext, registerLocaleIpc; wire AbortController into generate handler; keep codesign:v1:cancel-generation handler - store.ts: merge all origin/main state (selectedElement, inputFiles, referenceUrl, i18n tr(), applyInlineComment, etc.) with PR's activeGenerationId / cancelGeneration / finishIfCurrent guards - store.test.ts: update sendPrompt calls to object form; add designSystem: null to OnboardingState fixture; relax toast title assertion for i18n Signed-off-by: hqhq1025 <1506751656@qq.com>
…in features Rebase onto origin/main exposed conflicts where PRs #18, #19, #20 had advanced store, preload, shared and main/index.ts beyond the branch point. Resolve by taking origin/main as the base and layering our cancellation additions on top: - shared/index.ts: add LocalInputFile, SelectedElement, ElementSelectionRect, ApplyCommentPayload, StoredDesignSystem exports; add generationId to GeneratePayload; keep CancelGenerationPayloadV1 - preload/index.ts: add locale API, pickInputFiles, pickDesignSystemDirectory, clearDesignSystem, applyComment; add generationId to generate payload; keep cancelGeneration with schemaVersion: 1 - main/index.ts: add applyComment, scanDesignSystem, preparePromptContext, registerLocaleIpc; wire AbortController into generate handler; keep codesign:v1:cancel-generation handler - store.ts: merge all origin/main state (selectedElement, inputFiles, referenceUrl, i18n tr(), applyInlineComment, etc.) with PR's activeGenerationId / cancelGeneration / finishIfCurrent guards - store.test.ts: update sendPrompt calls to object form; add designSystem: null to OnboardingState fixture; relax toast title assertion for i18n Signed-off-by: hqhq1025 <1506751656@qq.com>
* feat(desktop): redesign sidebar input + add cancellation IPC - Replace single-line input with autosize textarea (1–6 rows, max-h 144 px) - Move send button into bottom-right corner of textarea container, icon-only 28 px - During generation, send button becomes a stop button (Square icon) wired to cancelGeneration - Remove redundant keyboard-hint bar; fold hint into placeholder text - Enter sends, Shift+Enter inserts newline, ⌘↵ global send preserved - main: extract runGenerate helper; maintain Map<id, AbortController> for in-flight requests; new codesign:cancel-generation IPC handler; logger scope 'ipc' - preload: expose cancelGeneration(id) on window.codesign - shared: GeneratePayload accepts optional generationId - store: add activeGenerationId state + cancelGeneration action; suppress abort errors silently without pushing an error toast Signed-off-by: hqhq1025 <1506751656@qq.com> * fix: harden generation cancellation flows Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com> Signed-off-by: hqhq1025 <1506751656@qq.com> * fix: version cancellation ipc and tokenise sidebar Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com> Signed-off-by: hqhq1025 <1506751656@qq.com> * fix: preserve current-generation failures Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com> Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): guard cancel race + version IPC payload + add tests - applyGenerateSuccess/applyGenerateError already guarded via finishIfCurrent and the early-return id check; confirm with tests - Add noop test: unknown generationId leaves all in-flight intact - Add schema rejection test: empty generationId and missing schemaVersion both throw via CancelGenerationPayloadV1.parse - Fix biome formatting in store.test.ts (pre-existing) Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): properly merge rebase onto main — restore all origin/main features Rebase onto origin/main exposed conflicts where PRs #18, #19, #20 had advanced store, preload, shared and main/index.ts beyond the branch point. Resolve by taking origin/main as the base and layering our cancellation additions on top: - shared/index.ts: add LocalInputFile, SelectedElement, ElementSelectionRect, ApplyCommentPayload, StoredDesignSystem exports; add generationId to GeneratePayload; keep CancelGenerationPayloadV1 - preload/index.ts: add locale API, pickInputFiles, pickDesignSystemDirectory, clearDesignSystem, applyComment; add generationId to generate payload; keep cancelGeneration with schemaVersion: 1 - main/index.ts: add applyComment, scanDesignSystem, preparePromptContext, registerLocaleIpc; wire AbortController into generate handler; keep codesign:v1:cancel-generation handler - store.ts: merge all origin/main state (selectedElement, inputFiles, referenceUrl, i18n tr(), applyInlineComment, etc.) with PR's activeGenerationId / cancelGeneration / finishIfCurrent guards - store.test.ts: update sendPrompt calls to object form; add designSystem: null to OnboardingState fixture; relax toast title assertion for i18n Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): wait for cancel IPC ack + version generate payload Major #1: cancelGeneration now waits for the IPC round-trip before clearing isGenerating/activeGenerationId. If main rejects the cancel (bad id, already aborted) the error becomes visible via toast instead of being silently swallowed. Updated store test to reflect async cancel and drain microtasks between cancel and the follow-up sendPrompt. Major #2: add GeneratePayloadV1 (schemaVersion: 1, generationId required) to @open-codesign/shared and rename the IPC channel to codesign:v1:generate, matching the pattern established by codesign:v1:cancel-generation. Preload builds the v1 envelope with satisfies check; main validates with GeneratePayloadV1.parse(). Add 5 schema tests for missing/wrong schemaVersion and empty generationId. Add notifications.cancellationFailed i18n key. Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): restore legacy generate IPC shim + use ui tokens in sidebar - Add codesign:generate legacy handler that accepts old GeneratePayload (no schemaVersion) and promotes it to V1 by injecting schemaVersion:1 and falling back generationId to gen-${Date.now()} when absent. Emits a warn log to track removal in the next minor release. - Add --color-on-accent token to packages/ui tokens.css (light + dark) so accent-button text never hard-codes a color value. - Replace text-white in Sidebar.tsx with text-[var(--color-on-accent)] on both the stop and send IconButton variants. - Add 3 tests for GeneratePayload legacy schema and the V1 promotion path that the shim relies on. Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): surface error when renderer bridge missing during cancel Split the combined `!id || !window.codesign` guard in `cancelGeneration` into two separate checks. A missing `activeGenerationId` is a legitimate no-op, but a missing IPC bridge is a real error that must be surfaced via `errorMessage`/`lastError` and a toast. Adds a Vitest test to cover the new error path (with `beforeAll` i18n init so `tr()` keys resolve). Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): restore attachments/refURL/designSystem entries + i18n sidebar strings Restores the local context panel (attachments, reference URL, design system) that was accidentally dropped during rebase when the cancel button was added. All hardcoded English strings are now routed through useT(); chat.send and chat.stop keys were added to en.json / zh-CN.json. Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): fix import order and trailing blank line after rebase Biome import sort and formatter fixes introduced during rebase conflict resolution. Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): remove hardcoded textarea fallback, throw on missing tokens Removes FALLBACK_FONT_SIZE and FALLBACK_LINE_HEIGHT_MULTIPLIER constants from getTextareaLineHeight. When computed lineHeight is not a finite positive number and either fontSize or --leading-body token is absent or invalid, the function now throws instead of silently using magic numbers. The --leading-body token is already defined in packages/ui/src/tokens.css. Updates Sidebar.test.ts: replaces the "falls back" test with two tests — one verifying valid-token multiplication and one verifying the throw path. Signed-off-by: hqhq1025 <1506751656@qq.com> --------- Signed-off-by: hqhq1025 <1506751656@qq.com> Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com> Co-authored-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com>
The PR commits were originally based on an older version of the codebase and accidentally removed features added by PRs #18 and #20: - Restore design-system.ts and prompt-context.ts (deleted in branch) - Restore InlineCommentComposer.tsx and PreviewPane.test.ts (deleted) - Restore StoredDesignSystem / designSystem support in config.ts - Re-export StoredDesignSystem + STORED_DESIGN_SYSTEM_SCHEMA_VERSION from shared - Re-add getOnboardingState() / setDesignSystem() to onboarding-ipc.ts (these are needed by main/index.ts design-system IPC handlers) - Add full import set to main/index.ts (stat, basename, applyComment, dialog, scanDesignSystem, getCachedConfig, getOnboardingState, setDesignSystem, preparePromptContext) - Restore preload/index.ts: add back applyComment, pickInputFiles, pickDesignSystemDirectory, clearDesignSystem methods removed in PR; keep PR-added settings/preferences namespaces and interfaces - Restore all other files regressed to pre-#18/#20 state (Sidebar.tsx, store.ts, App.tsx, core/index.ts, providers, templates, ui tokens, etc.) - Fix Settings.tsx formatting (spurious blank line) - Fix store.test.ts READY_CONFIG missing designSystem field
…#16) * feat(desktop): settings tabs with multi-provider management and preferences persistence Implements all four settings tabs (Models, Appearance, Storage, Advanced) with full persistence and correct error handling. Key changes: - Settings UI: four-tab panel (Models / Appearance / Storage / Advanced) with provider cards, add/delete/activate flows, model selector, path viewer, reset - toProviderRows: soft-fail on safeStorage decryption — returns error:'decryption_failed' row instead of throwing; UI shows red badge + 'Re-enter key' button - preferences-ipc.ts: new IPC module persisting updateChannel and generationTimeoutSec to ~/.config/open-codesign/preferences.json (schemaVersion:1) - locale-ipc.ts and preferences-ipc.ts registered in main/index.ts - preload: settings, preferences, and locale namespaces exposed to renderer - AppearanceTab: language select reads/writes via locale:get-current / locale:set - AdvancedTab: update channel and generation timeout read/write via preferences IPC - ActiveModelSelector: 400ms debounce with proper useEffect cleanup on unmount - delete-provider: fix ghost active provider — when all providers removed, write tombstone config (empty secrets) instead of falling back to hardcoded 'openai'; re-adding a new provider correctly auto-activates it - provider-settings.ts and onboarding-ipc.ts: use electron-runtime import pattern - Tests: 8 total (5 new), covering decryption-failed row, masked-key row, ghost-provider auto-activate, provider preservation, assertProviderHasStoredSecret Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): switch model defaults on provider delete + guard validate against stale form - Extract computeDeleteProviderResult pure helper to provider-settings.ts; when the active provider is deleted the new active provider's PROVIDER_SHORTLIST defaults are used for modelPrimary/modelFast instead of carrying over the old provider's model IDs (Blocker from #16 review). - Tombstone path (last provider removed) now writes empty model strings so a stale model ID is never persisted. - Guard handleValidate in AddProviderModal: snapshot provider/apiKey/baseUrl before the async call and discard the result via applyValidateResult if the form changed while awaiting (Major race fix from #16 review). - Add applyValidateResult pure exported helper for unit testing without a DOM. - 5 new tests (3 computeDeleteProviderResult + 4 applyValidateResult, total 15). Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): version settings/preferences IPC + ui token compliance - Add settings:v1:* and preferences:v1:* versioned IPC channels (8 settings + 2 prefs) - Keep legacy settings:* and preferences:* channels as shims with logger.warn - Preload now calls v1 channels exclusively - Extract per-handler run* helpers shared by v1 and legacy registrations - Replace text-white with text-[var(--color-on-accent)] in Settings badges/buttons - Replace text-[10px] with text-[var(--font-size-badge)] in ProviderCard badges - Add --color-on-accent and --font-size-badge tokens to packages/ui/src/tokens.css - Add onboarding-ipc.test.ts: 3 tests covering v1 channel registration and legacy compat Signed-off-by: hqhq1025 <1506751656@qq.com> * fix: surface silent failures in locale/preferences/generate paths - Settings.tsx locale-load .catch(() => {}) → pushToast error - Settings.tsx preferences bootstrap .catch(() => {}) → pushToast error - preferences-ipc.ts non-ENOENT read errors → throw CodesignError (PREFERENCES_READ_FAILED) - packages/core generate() empty artifacts → throw CodesignError (OUTPUT_MISSING_ARTIFACT) - Add pushIframeError to CodesignState interface (was implemented but missing from type) - Fix store.test.ts: remove stale designSystem field not present in OnboardingState - Add preferences-ipc.test.ts: 2 tests covering ENOENT default and non-ENOENT throw - Add generate.test.ts: 4 tests covering OUTPUT_MISSING_ARTIFACT error path Signed-off-by: hqhq1025 <1506751656@qq.com> * fix(desktop): incorporate i18n dep and shared type additions from main Add @open-codesign/i18n workspace dep, LocalInputFile/ElementSelection types from main, and fix sendPrompt call signature in App.tsx to match the object-input type declared in store.ts. * fix(desktop): restore main-branch features removed during rebase The PR commits were originally based on an older version of the codebase and accidentally removed features added by PRs #18 and #20: - Restore design-system.ts and prompt-context.ts (deleted in branch) - Restore InlineCommentComposer.tsx and PreviewPane.test.ts (deleted) - Restore StoredDesignSystem / designSystem support in config.ts - Re-export StoredDesignSystem + STORED_DESIGN_SYSTEM_SCHEMA_VERSION from shared - Re-add getOnboardingState() / setDesignSystem() to onboarding-ipc.ts (these are needed by main/index.ts design-system IPC handlers) - Add full import set to main/index.ts (stat, basename, applyComment, dialog, scanDesignSystem, getCachedConfig, getOnboardingState, setDesignSystem, preparePromptContext) - Restore preload/index.ts: add back applyComment, pickInputFiles, pickDesignSystemDirectory, clearDesignSystem methods removed in PR; keep PR-added settings/preferences namespaces and interfaces - Restore all other files regressed to pre-#18/#20 state (Sidebar.tsx, store.ts, App.tsx, core/index.ts, providers, templates, ui tokens, etc.) - Fix Settings.tsx formatting (spurious blank line) - Fix store.test.ts READY_CONFIG missing designSystem field --------- Signed-off-by: hqhq1025 <1506751656@qq.com>
Summary
Validation