Files
orca/src/shared/browser-page-doc-location.ts
T
Jinwoo Hong cb848647e5 fix(browser-preview): require explicit preview capabilities (STA-5758) (#16921)
* fix(browser-preview): require explicit preview capabilities (STA-5758)

Scope document reads to approved directories, confirm external links before opening them, revoke grants with tab lifecycle, and keep document-preview session state rollback-safe across mixed client/runtime versions.

* Harden document preview lifecycle and permissions

* Document preview DNS prefetch residual

* Make preview E2E guest focus explicit

* fix(browser-preview): entry-file-only authority for root-level docs, contained chip layout, re-issued gate paths (STA-5758)

A grant whose document directory is its own request base — a doc at the
workspace root, or outside any workspace — now reads nothing but the entry
file until the reader approves a directory, at both the lexical and the
canonical containment pass. The DNS-prefetch residual can only beacon what
the page can read, and a root-level document could previously read the
whole worktree silently.

The identity chip's host badge overflowed the chip's layout box under
squeeze (Linux CI): every row member can now shrink and truncate, verified
by a width sweep in isolated Chromium down to ~120px chips.

The Allow banner says what it grants: 'Allow folder', reading files in the
named directory, for the life of the preview.

The reliability-gate manifest command, testFiles entry, assertion refs and
dated evidence naming the deleted doc-preview-external-link-bridge.test.ts
are re-issued at doc-preview-external-link-confirmation.test.ts with a
fresh 189/189 run; the focus-gate assertion text follows the shipped gate.

* fix(browser-preview): hide the chip identity row below 24rem instead of clipping it, ellipsize the host badge, catalog the new i18n keys (STA-5758)

CI's preview pane leaves the chip ~40px: no truncation shows anything
there, so the Workspace-file label and host badge now hide whole below a
24rem container threshold sized so that visible implies contained. The
badge text gains an inner text box — text directly inside the flex pill
clipped both ends with no ellipsis. The e2e geometry oracle asserts
containment when the row shows and the threshold when it does not.

verify:localization-catalog: the hardening's new preview keys (and the
renamed allowDirectory) join en.json via sync:localization-catalog.

* feat(browser-preview): batch blocked folders into one access decision (STA-5758)

Sequential per-folder banners trained the allow reflex without adding
judgment — a reader cannot weigh assets/ against data/. The banner now
accumulates every folder a load surfaces, names them (three, then a
count, full list in the title), and grants exactly that set with one
Allow-N-folders click and one reload. Dismiss fences the whole named
set. The map lives behind a ref with a version tick so a dismissal
fences an offer landing in the same event batch.
2026-08-28 00:42:07 -04:00

52 lines
1.6 KiB
TypeScript

import type {
BrowserPage,
BrowserPageDocLocation,
BrowserWorkspace
} from './browser-workspace-types'
import { relativePathInsideRoot, resolveRuntimePath } from './cross-platform-path'
/**
* Why an explicit comparison and not object identity: the mirror rebuilds the workspace's copy of
* this on every page change, so identity is never equal and the store would write a new workspace
* object — and republish the tab — on every unrelated update.
*/
export function browserPageDocLocationsEqual(
left: BrowserPageDocLocation | null,
right: BrowserPageDocLocation | null
): boolean {
if (left === null || right === null) {
return left === right
}
return left.worktreeId === right.worktreeId && left.filePath === right.filePath
}
/** True for a page or tab that shows a workspace document rather than a URL. */
export function isWorkspaceDocSurface(
surface: Pick<BrowserPage | BrowserWorkspace, 'docLocation'>
): boolean {
return Boolean(surface.docLocation)
}
export function remapBrowserPageDocLocation(
location: BrowserPageDocLocation,
oldWorktreeId: string,
newWorktreeId: string,
oldWorktreePath?: string,
newWorktreePath?: string
): BrowserPageDocLocation {
if (location.worktreeId !== oldWorktreeId) {
return location
}
const relativePath =
oldWorktreePath && newWorktreePath
? relativePathInsideRoot(oldWorktreePath, location.filePath)
: null
return {
...location,
worktreeId: newWorktreeId,
...(relativePath !== null && newWorktreePath
? { filePath: resolveRuntimePath(newWorktreePath, relativePath) }
: {})
}
}