Skip to content

feat(cloud-preview): ✨ Improve cloud preview integration#61

Merged
jorben merged 2 commits intomasterfrom
chore/workspace-changes-20260305
Mar 5, 2026
Merged

feat(cloud-preview): ✨ Improve cloud preview integration#61
jorben merged 2 commits intomasterfrom
chore/workspace-changes-20260305

Conversation

@jorben
Copy link
Collaborator

@jorben jorben commented Mar 5, 2026

Summary

  • Expand cloud preview workflow by wiring new IPC channel support and updating cloud service behavior.
  • Upgrade preview and cloud preview pages with improved UI flow, styling, and end-to-end renderer integration.
  • Add multi-language locale updates and strengthen unit/renderer tests for cloud preview and file handler paths.

Test Plan

  • Run npm run lint
  • Run npm test
  • Verify cloud preview flow manually in Electron (npm run dev)

🤖 Generated with Codex Cli

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

AI Code Review Summary

PR: #61 (feat(cloud-preview): ✨ Improve cloud preview integration)
Preferred language: English

Overall Assessment

Detected 16 actionable findings, prioritize CRITICAL/HIGH before merge.

Major Findings by Severity

  • HIGH (3)
    • src/core/infrastructure/services/CloudService.ts:40 - Missing unit tests for complex Content-Disposition filename parsing and encoding fallbacks
    • src/main/ipc/handlers/file.handler.ts:123 - Missing IPC handler tests for copyImageToClipboard input classes and error paths
    • src/renderer/pages/CloudPreview.tsx:348 - PDF download action calls markdown download API
  • MEDIUM (10)
    • src/main/ipc/handlers/tests/file.handler.test.ts:125 - Missing assertion that only one nativeImage factory is used per source type
    • src/main/ipc/handlers/file.handler.ts:28 - IPC handler allows arbitrary local file path reads via copyImageToClipboard
    • src/main/ipc/handlers/file.handler.ts:25 - Renderer-controlled local file path can be read via clipboard IPC without allowlist constraints
    • src/renderer/pages/tests/CloudPreview.test.tsx:1061 - Missing assertion of user-facing success feedback for copy actions
    • src/renderer/pages/tests/Preview.test.tsx:360 - No boundary-case coverage when no image source exists for Copy Image
    • src/renderer/pages/CloudPreview.tsx:476 - No tests indicated for new clipboard copy success/failure branches
    • src/renderer/pages/CloudPreview.tsx:582 - No tests indicated for new download dropdown behavior and state gating
    • src/renderer/pages/Preview.tsx:368 - No tests indicated for new copy actions in local Preview page
  • LOW (3)
    • src/main/ipc/handlers/tests/file.handler.test.ts:122 - Clipboard tests do not verify the image object passed to writeImage
    • src/main/ipc/handlers/tests/file.handler.test.ts:122 - Potential flakiness from missing mock reset expectations around clipboard/nativeImage call counts
    • src/renderer/pages/tests/CloudPreview.test.tsx:1248 - Dropdown interaction test uses brittle text+closest selector pattern

Actionable Suggestions

  • In file.handler.ts, enforce path allowlisting/canonicalization before nativeImage.createFromPath.
  • Consider replacing raw imageSource input with an internal token or task/page reference resolved in main process.
  • Add focused unit tests for CloudService filename extraction helpers and malformed header inputs.
  • Add IPC integration tests for copyImageToClipboard success/failure paths (data URL, disallowed URL, disallowed path).
  • In copyImageToClipboard, replace raw imageSource input with { taskId, page } and resolve image path exclusively in main process from trusted metadata.
  • If raw path must remain, enforce realpath + strict prefix allowlist (e.g., app upload/image directories) and reject everything else.
  • Add telemetry/audit logs for rejected/accepted clipboard image source types to detect abuse patterns.
  • Create a dedicated test matrix for Content-Disposition parsing scenarios (RFC5987/plain/malformed/dual headers).
  • Add unit tests for mojibake repair heuristic with explicit positive and negative fixtures.
  • Add IPC handler tests with mocked Electron modules to keep tests deterministic and non-flaky.
  • Add a small integration/contract test validating channel constant -> preload invoke -> handler registration consistency.
  • Include observability assertions (error message stability) so renderer UX does not regress silently.

Potential Risks

  • Privileged IPC endpoint may be abused for unintended local file access if renderer is compromised.
  • Filename decoding logic changes may alter output filenames for some legacy/nonstandard servers.
  • Compromised renderer can abuse permissive local path handling in IPC to expand access to local resources.
  • Clipboard-based exfiltration channels can aid attacker workflows after initial compromise.
  • Silent filename corruption for non-ASCII downloads
  • Clipboard feature working in dev but failing in packaged builds/platform variants
  • Runtime IPC mismatch not caught by TypeScript-only checks
  • False negatives in tests due to weak assertions on side-effect payloads.
  • Edge-case parsing variants may remain untested in filename decoding logic despite improved coverage.
  • Local file access abuse via IPC if renderer trust boundary is weak.
  • Information disclosure via detailed error propagation.
  • Path traversal/file clobbering risk if decoded filenames are unsafely reused.

Test Suggestions

  • CloudService: table-driven tests for Content-Disposition variants (filename*, filename, malformed, mixed).
  • IPC: tests for COPY_IMAGE_TO_CLIPBOARD validating input sanitization and expected response structure.
  • End-to-end smoke test invoking preload API and confirming success/error handling in renderer.
  • Create security-focused IPC tests for path canonicalization and allowlist enforcement.
  • Add unit tests for createImageFromSource covering data:image, http(s), file://, relative/absolute paths, UNC-like patterns.
  • Run threat-model regression for renderer-to-main IPC endpoints whenever new file-related channels are introduced.
  • CloudService download filename extraction table-driven tests
  • file.handler copy-image IPC branch coverage tests
  • Preload-to-channel invocation contract test
  • Negative tests for remote URL rejection and empty image handling
  • Run npm run test:unit and ensure new tests are deterministic across OS path formats.
  • Add targeted cases for Content-Disposition precedence and RFC5987 language-tag variants.

File-Level Coverage Notes

  • src/core/infrastructure/services/CloudService.ts: High testing gap: newly introduced filename extraction logic is complex and branch-heavy; strong need for boundary and regression tests. (No test diffs were provided for this new logic.)
  • src/main/ipc/handlers/file.handler.ts: High testing gap: new clipboard IPC has multiple source-type branches and Electron runtime dependencies that need deterministic mocks. (Particularly important to avoid flaky tests by fully mocking Electron APIs.)
  • src/shared/ipc/channels.ts: Low direct risk, but should be covered by IPC wiring/contract tests due to string-key coupling. (Single-line constant additions often fail only at runtime if not integration-tested.)
  • src/preload/index.ts: Medium testing gap: new API surface requires preload contract tests. (No preload-specific tests shown for new method.)
  • src/preload/electron.d.ts: Type declaration update appears consistent; runtime confidence depends on IPC wiring tests. (Declaration-only changes are safe if runtime tests exist.)
  • src/renderer/electron.d.ts: Type declaration additions look coherent; primary risk remains untested runtime behavior. (Consider ensuring IpcResponse generic aligns with actual handler returns on failures.)
  • src/core/infrastructure/services/tests/CloudService.test.ts: Strong test expansion for RFC5987 decoding and mojibake recovery. Coverage meaningfully improved for i18n filename handling. (Overall this file is in good shape for this change set.)
  • src/main/ipc/handlers/tests/file.handler.test.ts: Comprehensive new tests for file:copyImageToClipboard happy and error paths; major coverage gain. (Most critical functional paths are already covered.)
  • src/main/ipc/tests/handlers.test.ts: Channel mock updated consistently to include new IPC constant; low risk. (No direct behavior change tested here.)
  • src/renderer/pages/CloudPreview.tsx: Feature additions are substantial and user-facing, but testing evidence is missing for key async and conditional branches. (Consider centralized test helpers for mocking window.api and clipboard to reduce flakiness.)
  • src/renderer/pages/Preview.tsx: Added copy controls improve UX but need explicit coverage for async clipboard/IPC behavior and disabled states. (Also validate aria-label based queries to keep tests resilient and accessibility-aware.)
  • src/renderer/pages/tests/CloudPreview.test.tsx: Coverage improved significantly for copy/download and cloud proxy branches. Remaining issues are mostly robustness and completeness of UX assertions. (No blocking test defects, but medium-value hardening opportunities remain.)
  • src/renderer/pages/tests/Preview.test.tsx: New copy action tests are valuable and cover core happy/error flows. (Selectors were improved in at least one place (getByRole('img')), which reduces flakiness.)
  • tests/setup.renderer.ts: Test setup additions correctly enable new clipboard and IPC mocks and improve baseline determinism. (No immediate testing risk from this change.)

Inline Downgraded Items (processed but not inline)

  • None

Coverage Status

  • Target files: 14
  • Covered files: 14
  • Uncovered files: 0
  • No-patch/binary covered as file-level: 0
  • Findings with unknown confidence (N/A): 0

Uncovered list:

  • None

No-patch covered list:

  • None

Runtime/Budget

  • Rounds used: 1/4
  • Planned batches: 4
  • Executed batches: 4
  • Sub-agent runs: 10
  • Planner calls: 1
  • Reviewer calls: 10
  • Model calls: 11/64
  • Structured-output summary-only degradation: NO

Copy link

@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.

Automated PR review completed.

  • Findings kept: 17
  • Findings with unknown confidence: 0
  • Inline comments attempted: 17
  • Target files: 14
  • Covered files: 14
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.


private constructor() {}

private extractDownloadFileName(contentDisposition: string, fallback: string): string {

This comment was marked as outdated.

return nativeImage.createFromDataURL(imageSource);
}

if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {

This comment was marked as outdated.


setDownloading(true);
try {
const result = await cloudContext.downloadResult(id);

This comment was marked as outdated.

return value;
}

private percentDecodeToBytes(input: string): number[] {

This comment was marked as outdated.

registerFileHandlers()
})

describe('file:copyImageToClipboard', () => {

This comment was marked as outdated.

FILE: {
GET_IMAGE_PATH: 'file:getImagePath',
DOWNLOAD_MARKDOWN: 'file:downloadMarkdown',
COPY_IMAGE_TO_CLIPBOARD: 'file:copyImageToClipboard',

This comment was marked as outdated.

data: { copied: true },
})
expect(mockNativeImage.createFromPath).toHaveBeenCalledWith('/tmp/page.png')
expect(mockClipboard.writeImage).toHaveBeenCalled()

This comment was marked as outdated.

renderPage(ctx)

await waitFor(() => {
const img = document.querySelector('img') as HTMLImageElement

This comment was marked as outdated.

)

await waitFor(() => {
const img = document.querySelector('img')

This comment was marked as outdated.

)

await waitFor(() => {
const img = document.querySelector('img')

This comment was marked as outdated.

Copy link

@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.

Automated PR review completed.

  • Findings kept: 16
  • Findings with unknown confidence: 0
  • Inline comments attempted: 16
  • Target files: 14
  • Covered files: 14
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.


private constructor() {}

private extractDownloadFileName(contentDisposition: string, fallback: string): string {
Copy link

Choose a reason for hiding this comment

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

[HIGH] Missing unit tests for complex Content-Disposition filename parsing and encoding fallbacks

A large, multi-branch parsing pipeline replaced a simple filename regex, but no tests are shown to validate behavior across valid/invalid headers, charsets, and fallback interactions.

Suggestion: Add focused unit tests for extractDownloadFileName via public method behavior (download result filename), covering: RFC5987 utf-8/latin1, malformed percent-encoding, quoted/unquoted plain filename, both filename* and filename precedence, empty/missing header fallback (task-${id}.pdf), mojibake repair positive/negative cases, and sanitization/path traversal/control chars.

Risk: Incorrect filenames in downloads, silent fallback to wrong names, and regressions on internationalized filenames (especially CJK/latin1 edge cases).

Confidence: 0.93

[From SubAgent: testing]

/**
* Copy image to clipboard
*/
ipcMain.handle(
Copy link

Choose a reason for hiding this comment

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

[HIGH] Missing IPC handler tests for copyImageToClipboard input classes and error paths

New Electron IPC behavior adds security-sensitive source validation and environment-dependent image creation, but no tests are shown for accepted/rejected sources and response contract stability.

Suggestion: Add handler-level tests with mocked ipcMain.handle, nativeImage, and clipboard: (1) missing source -> error, (2) data URL success, (3) file:// source conversion path, (4) plain local path success, (5) http/https rejected, (6) isEmpty() true returns validation error, (7) thrown error returns {success:false,error}.

Risk: Flaky or broken clipboard UX across platforms, accidental acceptance of disallowed inputs, and unstable renderer-facing error handling.

Confidence: 0.95

[From SubAgent: testing]


setDownloading(true);
try {
const result = await cloudContext.downloadResult(id);
Copy link

Choose a reason for hiding this comment

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

[HIGH] PDF download action calls markdown download API

The new handleDownloadPdf uses cloudContext.downloadResult(id), which appears to be the same endpoint used for markdown download, so the PDF menu item may download the wrong artifact.

Suggestion: Use a dedicated PDF download method (e.g., cloudContext.downloadPdf(id)) or pass an explicit format parameter (e.g., downloadResult(id, 'pdf')). Ensure the backend/IPC handler differentiates output type and filename/MIME.

Risk: Users selecting “Download PDF” may receive markdown instead, causing functional mismatch and confusion.

Confidence: 0.98

[From SubAgent: general]

expect(mockClipboard.writeImage).toHaveBeenCalledTimes(1)
})

it('should copy image from data URL successfully', async () => {
Copy link

Choose a reason for hiding this comment

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

[MEDIUM] Missing assertion that only one nativeImage factory is used per source type

Tests validate successful path selection but not exclusivity, so a bug invoking multiple decoding branches could pass unnoticed.

Suggestion: For each input type test, add negative assertions such as expect(mockNativeImage.createFromPath).not.toHaveBeenCalled() (for data URL), and similarly for other factories.

Risk: Regression in branch routing may cause incorrect parsing, extra work, or surprising side effects without failing tests.

Confidence: 0.93

[From SubAgent: testing]

return nativeImage.createFromPath(fileURLToPath(normalizedSource));
}

return nativeImage.createFromPath(normalizedSource);
Copy link

Choose a reason for hiding this comment

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

[MEDIUM] IPC handler allows arbitrary local file path reads via copyImageToClipboard

The new IPC endpoint blocks remote URLs, but still accepts arbitrary absolute/relative local paths from renderer input. This can be abused to probe/read local files as images (or sensitive paths) through privileged main-process file access.

Suggestion: Restrict accepted sources to a trusted allowlist (e.g., only app-managed task image directories), and validate canonicalized paths against that base. Consider rejecting plain paths entirely and only accepting internally-issued IDs or prevalidated file:// paths. Return a generic error for disallowed paths.

Risk: Renderer-controlled input can cause the main process to access unintended local filesystem locations, increasing attack surface and weakening sandbox boundaries.

Confidence: 0.89

[From SubAgent: general]

FILE: {
GET_IMAGE_PATH: 'file:getImagePath',
DOWNLOAD_MARKDOWN: 'file:downloadMarkdown',
COPY_IMAGE_TO_CLIPBOARD: 'file:copyImageToClipboard',
Copy link

Choose a reason for hiding this comment

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

[MEDIUM] Missing contract/integration tests for new IPC channel exposure across channels/preload/types

The new IPC operation spans channel constants, preload bridge, and TS interfaces, but no tests are shown to validate end-to-end wiring and runtime channel name consistency.

Suggestion: Add a lightweight integration test (or contract test) asserting renderer call window.api.file.copyImageToClipboard invokes the exact channel key and returns typed {copied:true} payload on success; include negative contract for error propagation.

Risk: Runtime mismatches between declared API and actual handler/channel wiring that compile but fail at execution.

Confidence: 0.90

[From SubAgent: testing]

window.HTMLElement.prototype.scrollIntoView = vi.fn()
}

Object.defineProperty(navigator, 'clipboard', {
Copy link

Choose a reason for hiding this comment

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

[MEDIUM] Global clipboard mock is not reset per test and can leak call history/implementation

navigator.clipboard.writeText is created once at module init and not re-initialized in beforeEach, so test-specific overrides (e.g., mockRejectedValueOnce) and call state may leak or become order-dependent across suites.

Suggestion: Recreate/reset clipboard mock in beforeEach similarly to other window.api mocks (or call vi.mocked(navigator.clipboard.writeText).mockReset().mockResolvedValue(undefined) in beforeEach). This keeps each test isolated and deterministic.

Risk: Flaky tests, hidden inter-test coupling, and false positives/negatives when copy-related tests are run in different orders.

Confidence: 0.94

[From SubAgent: general]

data: { copied: true },
})
expect(mockNativeImage.createFromPath).toHaveBeenCalledWith('/tmp/page.png')
expect(mockClipboard.writeImage).toHaveBeenCalledTimes(1)
Copy link

Choose a reason for hiding this comment

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

[LOW] Clipboard tests do not verify the image object passed to writeImage

New success-path tests only check that writeImage was invoked, not that it received the created native image. A handler bug could call writeImage with the wrong object (or undefined) while these tests still pass.

Suggestion: Strengthen assertions with expect(mockClipboard.writeImage).toHaveBeenCalledWith(fakeImage) (or the specific instance for each path/dataURL case). Keep the count assertion as secondary.

Risk: Reduced test effectiveness; regressions in argument wiring may slip through CI.

Confidence: 0.94

[From SubAgent: general]

data: { copied: true },
})
expect(mockNativeImage.createFromPath).toHaveBeenCalledWith('/tmp/page.png')
expect(mockClipboard.writeImage).toHaveBeenCalledTimes(1)
Copy link

Choose a reason for hiding this comment

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

[LOW] Potential flakiness from missing mock reset expectations around clipboard/nativeImage call counts

Call-count assertions on shared mocks are vulnerable if cleanup/reset policy changes or if setup invokes these mocks unexpectedly.

Suggestion: Ensure beforeEach(() => vi.clearAllMocks()) (or resetAllMocks) is explicitly present in this suite and keep assertions robust with precondition checks.

Risk: Inter-test state leakage can produce non-deterministic failures and brittle CI behavior.

Confidence: 0.85

[From SubAgent: testing]


await waitFor(() => {
expect(screen.getByText('Download MD')).toBeInTheDocument()
const downloadButton = screen.getByText('Download').closest('button')
Copy link

Choose a reason for hiding this comment

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

[LOW] Dropdown interaction test uses brittle text+closest selector pattern

Using getByText(...).closest('button') is fragile with nested markup/localization changes and can produce intermittent failures.

Suggestion: Use role-based selectors directly, e.g. getByRole('button', { name: 'Download' }), and consider userEvent.click for realistic dropdown interaction.

Risk: Minor DOM/i18n changes may break tests without functional regression.

Confidence: 0.88

[From SubAgent: testing]

@jorben jorben merged commit 656d17d into master Mar 5, 2026
3 checks passed
@jorben jorben deleted the chore/workspace-changes-20260305 branch March 5, 2026 04:29
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