Skip to content

feat(desktop): add input context and inline comments#18

Merged
Sun-sunshine06 merged 1 commit intomainfrom
feat/p0-input-design-system-inline-comment
Apr 18, 2026
Merged

feat(desktop): add input context and inline comments#18
Sun-sunshine06 merged 1 commit intomainfrom
feat/p0-input-design-system-inline-comment

Conversation

@Sun-sunshine06
Copy link
Copy Markdown
Collaborator

@Sun-sunshine06 Sun-sunshine06 commented Apr 18, 2026

Summary

  • add sidebar support for local files, reference URLs, and design system repos
  • scan design system repos into prompt context and pass them into generate/apply-comment flows
  • add preview selection + inline comment editing MVP in the desktop app

Validation

  • corepack pnpm --filter @open-codesign/shared typecheck
  • corepack pnpm --filter @open-codesign/desktop typecheck
  • corepack pnpm --filter @open-codesign/core test

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-comment proceed 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, evidence apps/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 designSystem payload is written into config.toml without 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, evidence packages/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, evidence apps/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 designSystem snapshot, and unbounded reference-URL reads in the main process. Separately, the GitHub commit metadata for ebb7e2e7b6fe70f23ec517920195d1ce8dd095cb does not show a Signed-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, and corepack pnpm --filter @open-codesign/shared typecheck could not run in this runner because node_modules are absent; the environment also reports Node v20.20.2 while the repo requires Node 22+.

open-codesign Bot

Comment thread apps/desktop/src/main/prompt-context.ts Outdated
...(description ? { description } : {}),
excerpt: cleanText(stripHtml(html), MAX_URL_EXCERPT_CHARS),
};
} catch {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 },
  );
}

Comment thread packages/shared/src/config.ts Outdated
});
export type BaseUrlRef = z.infer<typeof BaseUrlRef>;

export const StoredDesignSystem = z.object({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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',
  );
}

@Sun-sunshine06 Sun-sunshine06 force-pushed the feat/p0-input-design-system-inline-comment branch from ebb7e2e to 27e65e7 Compare April 18, 2026 11:11
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, evidence packages/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] PreviewPane now trusts any window.postMessage payload with the right shape, but the generated preview runs untrusted JavaScript under sandbox="allow-scripts" and can forge those messages itself. That lets artifact code spoof element-selection and iframe-error events in the parent UI, evidence apps/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 postMessage payloads 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+, pnpm is not on PATH, node_modules are absent, and corepack pnpm vitest run apps/desktop/src/main/prompt-context.test.ts packages/core/src/generate.test.ts packages/shared/src/config.test.ts failed with Command "vitest" not found.
  • Missing coverage: not found in the touched tests for oversized-attachment handling or renderer-side postMessage source validation around inline comments / iframe errors.

open-codesign Bot

Comment thread apps/desktop/src/main/prompt-context.ts Outdated
const extension = extname(file.name).toLowerCase();
let buffer: Buffer;
try {
buffer = await readFile(file.path);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

Comment thread packages/core/src/index.ts Outdated
if (attachments.length === 0) return null;
const body = attachments
.map((file, index) => {
const lines = [`${index + 1}. ${file.name} (${file.path})`];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@Sun-sunshine06 Sun-sunshine06 force-pushed the feat/p0-input-design-system-inline-comment branch from 27e65e7 to 7b2797a Compare April 18, 2026 11:23
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] IPC contract changes are still unversioned — GeneratePayload gained new fields and three new IPC channels were added without a v1 namespace or schemaVersion, which violates the repo's compatibility/upgradeability rule and makes the next payload/channel change a breaking swap instead of a parallel rollout, evidence packages/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.ok and 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, evidence apps/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 kbd styles add raw white/rgba values and inline font declarations, which violates the token-only constraint and will drift across themes, evidence apps/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.ts failed because the runner is on Node v20.20.2 while the repo requires Node 22+, node_modules are absent, and pnpm could not find vitest.
  • Missing coverage: not found in the touched tests for scanDesignSystem partial-failure/large-file handling or the new IPC contract surface around generate / applyComment / pick-*.

open-codesign Bot

});
export type SelectedElement = z.infer<typeof SelectedElement>;

export const GeneratePayload = z.object({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@Sun-sunshine06 Sun-sunshine06 merged commit 43df205 into main Apr 18, 2026
7 checks passed
@Sun-sunshine06 Sun-sunshine06 deleted the feat/p0-input-design-system-inline-comment branch April 18, 2026 11:38
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
…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>
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
…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>
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
* 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>
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
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
hqhq1025 added a commit that referenced this pull request Apr 18, 2026
…#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant