Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ Read the narrow source of truth before changing its area:

- Leave concise comments on public and non-obvious code: explain ownership, invariants, lifecycle boundaries, and tradeoffs. Follow the Observe rules for runtime diagnostics.
- Keep hand-written production files below 800 logical lines; extract a cohesive module before extending a file that exceeds it.
- Put Rust test bodies in dedicated test files. Use integration tests by default; private unit tests use the adjacent `<module>_tests.rs` convention. Shared integration helpers live in `tests/common/mod.rs`.

## Observe

Expand All @@ -35,10 +34,7 @@ Read the narrow source of truth before changing its area:

## Prove

- For a production behavior bug, make the closest real boundary test red, implement the fix, and rerun it. Model ACP chunks, updates, and replayed history rather than mocking away protocol semantics.
- Do not add or update tests whose only evidence is matching source text, literal CSS selectors or declarations, token values, pixel values, or other implementation constants. Test user-observable behavior at a real boundary; prove visual-only changes in the browser at the required viewports instead of encoding the current stylesheet in a test.
- For visual-only work, verify the affected interaction in the browser at relevant wide and narrow viewports. Shared UI changes require both; shell composition requires the default and override paths.
- Run the narrowest relevant repository check first, then broaden when a shared contract changes. Read the available scripts and tool configuration rather than copying commands into this guide.
Tests and verification: read [the testing guide](docs/testing.md) when writing tests, diagnosing test failures, or validating changes.

## Hand off

Expand Down
180 changes: 180 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions apps/web/frontend/webAppShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,38 @@ export function createWebAppShell(): FrontendShell {
reload: () => window.location.reload(),
},
fileViewer: true,
fileViewerDownloads: {
async save({ handle, label, operationId }, signal) {
const search = new URLSearchParams({
clientInstanceId: clientInstanceIdForBootstrap(bootstrap()),
fileViewerHandle: handle,
operationId,
});
const url = `/__openaide-app-server/download?${search}`;
// Only a metadata check uses fetch: file bytes stream directly to the browser's
// download manager, without buffering a potentially large artifact in the renderer.
const ready = await fetch(`${url}&check=1`, {
credentials: "same-origin",
cache: "no-store",
signal: AbortSignal.any([signal, AbortSignal.timeout(15_000)]),
});
if (ready.status !== 204) {
switch (ready.status) {
case 400: return "notAFile";
case 401:
case 403: return "permissionDenied";
case 404: return "notFound";
default: return "unavailable";
}
}
signal.throwIfAborted();
const link = document.createElement("a");
link.href = url;
link.download = label;
link.click();
return "started";
},
},
taskNotifications,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

Status: accepted

Web and Desktop use one shared read-only File Viewer over the local App Server's filesystem. A user-triggered absolute path is validated by App Server and exchanged for an opaque viewer handle; the viewer loads a point-in-time UTF-8 text snapshot capped at a 1 MiB prefix with an explicit truncated state when the file is larger, refreshes only on explicit user request for the active File Tab, renders Markdown files as GFM preview without Mermaid, renders other text/source with line numbers and basename-inferred highlighting, renders relative Markdown file links as File Tabs, and sends HTTPS links to the browser or shell. Heading and line fragments only position the current snapshot. Tabs show the basename; the header shows the full path as an App Server display label with Copy path, never as a read capability. File Tabs are Frontend presentation for the current Task Page: they are discarded on Task switch, reload, or leaving the page, App Server keeps no open-tab list, and v1 has no product tab cap. A failed load keeps the tab open with the reason, Retry, and Close, and does not touch Composer. Archived Tasks may still open files; the snapshot is current disk at click or refresh time, not frozen at archive. PNG, JPEG, WebP, and GIF files use the shared image inspection surface (Fit, zoom, and pan) under the same 5 MB signature check as Tool image preview. Other binary, non-UTF-8, and unsupported content receives a fallback state rather than a lossy decode, hex view, or unbounded read. Directories and other non-regular files use the load-failure tab; they are not a directory browser. A symlink to a regular file is followed for bytes; the header label stays the path the user opened. Dangling or looping links fail like other unreadable paths. This preserves arbitrary user-selected file access while keeping path authority in App Server and keeping the shared Frontend responsive and predictable.
Web and Desktop use one shared read-only File Viewer over the local App Server's filesystem. A user-triggered absolute path is validated by App Server and exchanged for an opaque viewer handle; the viewer loads a point-in-time UTF-8 text snapshot capped at a 1 MiB prefix with an explicit truncated state when the file is larger, refreshes only on explicit user request for the active File Tab, renders Markdown files as GFM preview without Mermaid, renders other text/source with line numbers and basename-inferred highlighting, renders relative Markdown file links as File Tabs, and sends HTTPS links to the browser or shell. Heading and line fragments only position the current snapshot. Tabs show the basename; the header shows the full path as an App Server display label with Copy path, never as a read capability. File Tabs are Frontend presentation for the current Task Page: they are discarded on Task switch, reload, or leaving the page, App Server keeps no open-tab list, and v1 has no product tab cap. A failed load keeps the tab open with the reason, Retry, and Close, and does not touch Composer. Archived Tasks may still open files; the snapshot is current disk at click or refresh time, not frozen at archive. PNG, JPEG, WebP, and GIF files use the shared image inspection surface (Fit, zoom, and pan). File Viewer previews are bounded to 2048 pixels on the long edge and 2 MiB of encoded bytes. Images already within both bounds retain their original bytes, including animation. Larger images receive an orientation-correct reduced preview; transparency is preserved, and animations use their first frame. Original downloads remain unchanged. Source reads are limited to 64 MiB, source dimensions to 16384 per axis and 32 million pixels, and decoder allocations to a best-effort 128 MiB budget with an explicit output-buffer reservation. Conversion runs outside the shared protocol lock with one image admitted at a time, keeping unrelated requests responsive and bounding aggregate conversion memory. Other binary, non-UTF-8, and unsupported content receives a fallback state rather than a lossy decode, hex view, or unbounded read. Directories and other non-regular files use the load-failure tab; they are not a directory browser. A symlink to a regular file is followed for bytes; the header label stays the path the user opened. Dangling or looping links fail like other unreadable paths. This preserves arbitrary user-selected file access while keeping path authority in App Server and keeping the shared Frontend responsive and predictable.
6 changes: 5 additions & 1 deletion docs/adr/0038-file-viewer-app-server-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@

Status: accepted

Web and Desktop talk to App Server with `fileViewer/open` (originating absolute path and optional line), `fileViewer/openFromHandle` (handle plus relative href or fragment), `fileViewer/refresh`, and `fileViewer/release`. After that first open, Frontend never reads with a raw path. The snapshot result carries handle, display path, basename, UTF-8 text or an image preview (PNG, JPEG, WebP, GIF, 5 MB, same inspection surface as Tool image preview) or fallback/error, truncated flag, and optional language. VS Code does not use these methods.
Web and Desktop talk to App Server with `fileViewer/open` (originating absolute path and optional line), `fileViewer/openFromHandle` (handle plus relative href or fragment), `fileViewer/refresh`, and `fileViewer/release`. After that first open, Frontend never reads with a raw path. The snapshot result carries handle, display path, basename, UTF-8 text or an image preview (PNG, JPEG, WebP, GIF, bounded to 2048 pixels and 2 MiB, same inspection surface as Tool image preview) or fallback/error, truncated flag, and optional language. VS Code does not use these methods.

For Web downloads (#401), the authenticated HTTP download route accepts `fileViewerHandle` with the initialized `clientInstanceId`. The existing viewer handle is reusable until release and remains bound to its owning client. `check=1` checks readability without returning file bytes, allowing the tab to report a failure before browser handoff. The transfer request reopens the current regular file, streams its bytes without preview limits, and sets an attachment filename from the referenced basename. Neither request accepts a raw path. Errors after browser handoff belong to the browser's download manager.

The snapshot `truncated` flag also identifies a reduced-resolution/static image preview. Frontend labels that preview explicitly; the original filesystem file and download handle are unaffected. File reads and conversion execute outside the shared protocol lock after authorization; completion rechecks client ownership before returning contents.
6 changes: 5 additions & 1 deletion docs/adr/0039-file-viewer-v1-non-goals.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@

Status: accepted

v1 File Viewer does not include multi-line or range File Quotes, in-file search, hex view, directory browsing, live watching or auto-refresh, persisted File Tabs, snapshots frozen at archive time, File Viewer inside VS Code, download or save-as from the viewer, or sending a File Quote immediately. Those remain separate product decisions.
v1 File Viewer does not include multi-line or range File Quotes, in-file search, hex view, directory browsing, live watching or auto-refresh, persisted File Tabs, snapshots frozen at archive time, File Viewer inside VS Code, native save-as from the viewer, or sending a File Quote immediately. Those remain separate product decisions.

Issue #401 adds Download to the Web File Viewer tab, including unsupported files and preview errors. Download streams the current filesystem bytes independently of preview limits and preserves the referenced basename, including symlink aliases and Unicode subject to browser/OS restrictions. The existing client-bound file authority applies, including explicitly selected paths outside the workspace; only readable regular files download. Browser download UI owns transfer progress, while failures to start appear in the tab with an explicit retry. Desktop and VS Code behavior is unchanged.

In Web Chat, explicit Markdown file links accept binary extensions, spaces, and extensionless names. Inline-code path detection remains conservative, and external URLs retain browser behavior. A file reference is a path to the current file, not a stored historical artifact.
Loading
Loading