{"version":3,"file":"web.mjs","names":["isRecord","routes","MediaPreview","MediaPreview","activityGroupFiles","channelFileEdits","fileLinksFiles","fillFiles","leaderFiles"],"sources":["../../src/types/webFiles.ts","../../src/extensions/workspaces/sessions/(frontend)/activity-group/files.web.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/filesStore.ts","../../src/extensions/workspaces/sessions/(frontend)/channel/file-edits.web.ts","../../src/types/apiRoutes.ts","../../generated/client.ts","../../src/types/fileEditsApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/filesApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/fileView.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/CommentDraft.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/DeleteFileDialog.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/DiffView.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/SessionMediaPreview.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/FilePanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/file-links/_components/FilePreviewPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/file-links/_lib/fileLinks.ts","../../src/extensions/workspaces/sessions/(frontend)/file-links/files.web.ts","../../src/extensions/workspaces/sessions/(frontend)/fill/_components/FilesBrowser.tsx","../../src/extensions/workspaces/sessions/(frontend)/fill/_components/FilesActivitySection.tsx","../../src/extensions/workspaces/sessions/(frontend)/fill/Files.activity.files.web.tsx","../../src/extensions/workspaces/sessions/(frontend)/leader/files.web.ts","../../generated/web.ts"],"sourcesContent":["import type { FileEditTool } from './domain';\n\n/**\n * View types shared by this package's hub channel and its web plugin. Wire\n * JSON only, no node types, so the cockpit bundle can carry them; the\n * cockpit receives them as 'file_edits' channel payloads.\n *\n * The channel carries the list alone. One file's diffs and content are large\n * and only wanted once a reader opens the file, so those come from the session\n * API instead (see fileEditsApi.ts).\n */\n\n/** One file this session changed, as the activity dock lists it. */\nexport interface FilesItemView {\n  /** Absolute path, and the identity every API route takes back. */\n  path: string;\n  /** Path relative to the session's working directory, which is what a row shows. */\n  relPath: string;\n  /** The tool behind the most recent change. */\n  tool: FileEditTool;\n  /** When that change happened, epoch milliseconds. */\n  at: number;\n  /** How many changes this session recorded for the file. */\n  count: number;\n  /**\n   * Whether any change captured a baseline, so a diff can be shown at all. A\n   * file found by comparing the tree around a bash call has no \"before\", so it\n   * is listed and readable but never diffed.\n   */\n  diffable: boolean;\n}\n\n/** The frame type the hub publishes and the web plugin claims; declared in the doompiWeb block too. */\nexport const filesChannelType = 'file_edits';\n\n/** Footer status key whose presence shows the activity group; the extension publishes it. */\nexport const filesStatusKey = 'doom-file-edit-files';\n","import { defineActivityGroup } from '@agimon-ai/doompi-core/web';\n\nimport { filesStatusKey } from '../../../../../types/webFiles';\nexport default defineActivityGroup({\n  keys: 'e f',\n  statusKey: filesStatusKey,\n  hideWhenEmpty: true,\n  marksBackgroundWork: false,\n  order: 15,\n});\n","import { defineSessionStore } from '@agimon-ai/doompi-core/web';\n\nimport type { FileEditsDetailView } from '../../../../../types/fileEditsApi';\nimport { filesChannelType, type FilesItemView } from '../../../../../types/webFiles';\nimport type { FileComment } from './fileView';\n\n/**\n * One session's record: the hub's last report plus whatever this page does with\n * it. Records are immutable values; the reducer and every action return a new one.\n *\n * Detail is cached per file rather than per open tab, because a reader closes a\n * file and comes back to it, and refetching the same history for the same click\n * is work the session already did.\n */\nexport interface FilesSession {\n  items: FilesItemView[];\n  /** One file's fetched history, keyed by absolute path. */\n  detail: Readonly<Record<string, FileEditsDetailView>>;\n  /** Paths with a detail request in flight, so a panel can say it is loading. */\n  loading: readonly string[];\n  /** What a failed fetch or a refused save reported, keyed by absolute path. */\n  errors: Readonly<Record<string, string>>;\n  /** Review notes waiting to be sent, in the order they were written. */\n  comments: readonly FileComment[];\n}\n\nconst empty: FilesSession = { items: [], detail: {}, loading: [], errors: {}, comments: [] };\n\nexport const files = defineSessionStore<FilesSession>(empty);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isItem(value: unknown): value is FilesItemView {\n  return (\n    isRecord(value) &&\n    typeof value.path === 'string' &&\n    typeof value.relPath === 'string' &&\n    typeof value.tool === 'string' &&\n    typeof value.at === 'number' &&\n    typeof value.count === 'number'\n  );\n}\n\nexport interface FilesPayload {\n  items: FilesItemView[];\n}\n\n/** The plugin's session data channel: 'file_edits' payloads into the store. */\nexport const filesChannel = files.channel<FilesPayload>({\n  channel: filesChannelType,\n  parse(input) {\n    if (!isRecord(input) || !Array.isArray(input.items)) return null;\n    return { items: input.items.filter(isItem) };\n  },\n  // Only the list is replaced. The cached detail and the pending notes belong\n  // to files a reader opened, not to the list: a file the session deletes stops\n  // being listed, and pruning here would blank the tab still open on it and\n  // then refetch it on the next frame.\n  reduce: (current, { items }) => ({ ...current, items }),\n});\n\nexport function markLoading(sessionId: string, filePath: string): void {\n  files.update(sessionId, (current) =>\n    current.loading.includes(filePath)\n      ? current\n      : { ...current, loading: [...current.loading, filePath], errors: withoutKey(current.errors, filePath) },\n  );\n}\n\nexport function storeDetail(sessionId: string, detail: FileEditsDetailView): void {\n  files.update(sessionId, (current) => ({\n    ...current,\n    detail: { ...current.detail, [detail.path]: detail },\n    loading: current.loading.filter((path) => path !== detail.path),\n    errors: withoutKey(current.errors, detail.path),\n  }));\n}\n\nexport function storeError(sessionId: string, filePath: string, message: string): void {\n  files.update(sessionId, (current) => ({\n    ...current,\n    loading: current.loading.filter((path) => path !== filePath),\n    errors: { ...current.errors, [filePath]: message },\n  }));\n}\n\nexport function addComment(sessionId: string, comment: FileComment): void {\n  files.update(sessionId, (current) => ({ ...current, comments: [...current.comments, comment] }));\n}\n\nexport function removeComment(sessionId: string, id: string): void {\n  files.update(sessionId, (current) => ({\n    ...current,\n    comments: current.comments.filter((comment) => comment.id !== id),\n  }));\n}\n\nexport function clearComments(sessionId: string, filePath: string): void {\n  files.update(sessionId, (current) => ({\n    ...current,\n    comments: current.comments.filter((comment) => comment.path !== filePath),\n  }));\n}\n\nfunction withoutKey(source: Readonly<Record<string, string>>, key: string): Record<string, string> {\n  return Object.fromEntries(Object.entries(source).filter(([entry]) => entry !== key));\n}\n","import { defineChannelFile } from '@agimon-ai/doompi-core/web';\n\nimport { filesChannel } from '../_lib/filesStore';\nexport default defineChannelFile(filesChannel);\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/doompi-core/web';\n\nimport type { FileEditsDetailView, FileEditsPreviewView, FileEditsSaveView } from './fileEditsApi';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path. The build reads both off\n * `src/extensions/workspaces/sessions/(backend)/api/file-edits/`, so the mount\n * is stated once, by the folder that creates it. They used to be written here,\n * in the contract and again inside every URL the page built, and the copies\n * were free to disagree.\n *\n * This is the one file the routes, the Hono app and the browser all read, so a\n * path cannot move on one side alone.\n */\nexport default defineApiRoutes({\n  detail: {\n    method: 'GET',\n    path: '/detail',\n    query: ['path'],\n    response: apiResponse<FileEditsDetailView>(),\n  },\n  preview: {\n    method: 'GET',\n    path: '/preview',\n    query: ['path'],\n    response: apiResponse<FileEditsPreviewView>(),\n  },\n  save: {\n    method: 'PUT',\n    path: '/content',\n    response: apiResponse<FileEditsSaveView>(),\n  },\n  /** The same path as `save`; the method is the whole of the difference. */\n  remove: {\n    method: 'DELETE',\n    path: '/content',\n    query: ['path'],\n  },\n  /**\n   * The host's own bytes route, not this package's.\n   *\n   * `host: true` hangs it off the session root rather than under `/plugins`,\n   * which is how the cockpit already serves it. Declaring it here is the first\n   * time anything records that this package's previews depend on it.\n   */\n  file: {\n    method: 'GET',\n    path: '/file',\n    query: ['path'],\n    host: true,\n  },\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { createApiClient, sessionApiAddress } from '@agimon-ai/doompi-core/web';\nimport { sealedTransport } from '@agimon-ai/doompi-web-security/browser';\n\nimport routes from '../src/types/apiRoutes';\n\n/** Scopes the tree mounts 'file-edits' at. */\nexport const apiScopes = ['session'] as const;\n\nexport const api = createApiClient(routes, {\n  scopes: apiScopes,\n  basePath: 'file-edits',\n  transport: (input, init) => sealedTransport.fetch(input, init),\n  sessionAddress: sessionApiAddress,\n});\n","import type { FileEditOrigin, FileEditTool } from './domain';\n\n/**\n * The wire vocabulary this package's API shares with whatever calls it.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read: the cockpit plugin builds its URLs from these and the\n * routes answer them, so neither half can drift from the other.\n *\n * Routes live in `./apiRoutes`, which the contract, the Hono app and the\n * generated client all read. This file holds the shapes that travel over them.\n *\n * Opening a changed file wants its whole history at once, so `detail` answers\n * in one round trip rather than making the page stitch three reads together;\n * `content` takes the manual save back; `preview` reads a file the session\n * never changed, which has no history to answer with.\n */\n\n/** Where a host mounts this package's API; the segment after /api/plugins/. */\nexport const API_BASE_PATH = 'file-edits';\n\n/** Query parameter naming the file a request is about. */\nexport const PATH_QUERY_PARAM = 'path';\n\n/**\n * One line of a diff. The number is the new file's for context and additions\n * and the old file's for removals, so it always says where the line lives.\n */\nexport interface FileEditsDiffRow {\n  marker: '+' | '-' | ' ';\n  line: number;\n  content: string;\n}\n\n/**\n * A run of changed lines with its surrounding context. Hunks are sent apart\n * rather than joined by an elision marker, so the page draws the gap between\n * them instead of parsing one out of a string.\n */\nexport interface FileEditsDiffHunk {\n  /** The line number the hunk opens at. */\n  start: number;\n  rows: FileEditsDiffRow[];\n}\n\n/**\n * One change in a file's history, each rendered on its own so a file edited\n * four times shows four distinct diffs rather than one merged blur.\n */\nexport interface FileEditsVersionView {\n  /** Position in the file's history, oldest first, starting at 1. */\n  index: number;\n  tool: FileEditTool;\n  at: number;\n  origin: FileEditOrigin;\n  additions: number;\n  removals: number;\n  /** The changed lines of this one change; absent when no baseline was captured. */\n  hunks?: FileEditsDiffHunk[];\n  /** Why there is no diff, shown in its place. */\n  note?: string;\n}\n\n/** Everything this session did to the file: its first baseline against what is on disk now. */\nexport interface FileEditsCumulativeView {\n  additions: number;\n  removals: number;\n  hunks?: FileEditsDiffHunk[];\n  note?: string;\n}\n\n/** The file as it stands on disk right now, which is what the source view edits. */\nexport interface FileEditsWorkingView {\n  content: string;\n  /** sha256 of the content; the token a save hands back to prove it is not stale. */\n  hash: string;\n  /** True when the file is binary, missing, or past the size cap, so `content` is empty. */\n  unavailable: boolean;\n  reason?: string;\n}\n\n/** What the detail route answers with. */\nexport interface FileEditsDetailView {\n  path: string;\n  relPath: string;\n  versions: FileEditsVersionView[];\n  cumulative: FileEditsCumulativeView;\n  working: FileEditsWorkingView;\n}\n\n/**\n * What the preview route answers with: the file as it stands, and nothing\n * about how it got there. A file this session never touched has no history and\n * no baseline, so there is no diff to send and no save to take back.\n */\nexport interface FileEditsPreviewView {\n  path: string;\n  relPath: string;\n  working: FileEditsWorkingView;\n}\n\n/** What the save route takes. */\nexport interface FileEditsSaveRequest {\n  path: string;\n  /** The hash the reader loaded; a mismatch means the file moved underneath them. */\n  expectedHash: string;\n  content: string;\n}\n\n/** What the save route answers with. */\nexport interface FileEditsSaveView {\n  hash: string;\n}\n\n/** What a route reports when it refuses; the page shows `error` verbatim. */\nexport interface FileEditsErrorView {\n  error: string;\n  /** On a stale save, the hash the file actually has now. */\n  hash?: string;\n}\n","import { api } from '../../../../../../generated/client';\nimport {\n  type FileEditsDetailView,\n  type FileEditsPreviewView,\n  PATH_QUERY_PARAM,\n} from '../../../../../types/fileEditsApi';\n\n/**\n * The page's half of this package's session API: one file's history, the\n * manual save, and the deletion.\n *\n * These are adapters now, not a transport. The generated client owns the URL\n * and the sealed transport with it, so nothing here spells a route or reaches\n * for `fetch`. What stays is this package's own vocabulary: which refusals the\n * reader is shown, and the stale-save contract the editor depends on.\n *\n * Sealing still matters and still happens, one layer down. What travels here is\n * file contents in both directions, which is the worst thing in the cockpit to\n * hand a tunnel's relay in the clear.\n */\n\nconst UNREACHABLE = 'The session is unreachable.';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Where a file's raw bytes are served from, for the ones the page shows rather\n * than edits.\n *\n * This is the host's route, not this package's, which is why the route table\n * marks it `host: true` and it lands beside the session rather than under\n * `/plugins`. The cockpit already serves any file under a session's working\n * directory, and that route carries the traversal guard, the size cap and the\n * content-type mapping. A second one here would duplicate a security boundary\n * to avoid duplicating a URL, which is the wrong way round.\n */\nexport function sessionFileUrl(sessionId: string, relPath: string): string {\n  return api.session(sessionId).file.url({ query: { [PATH_QUERY_PARAM]: relPath } });\n}\n\n/** The message a reader is shown: the route's own words, or why there were none. */\nfunction messageOf(result: { status: number; error: string }): string {\n  if (result.status === 0) return UNREACHABLE;\n  return result.error === '' ? `The session answered ${result.status}.` : result.error;\n}\n\nexport type FetchDetailResult = { ok: true; detail: FileEditsDetailView } | { ok: false; error: string };\n\nexport async function fetchFileDetail(sessionId: string, filePath: string): Promise<FetchDetailResult> {\n  const result = await api.session(sessionId).detail({ query: { [PATH_QUERY_PARAM]: filePath } });\n  if (!result.ok) return { ok: false, error: messageOf(result) };\n  if (!isRecord(result.data)) return { ok: false, error: 'The session answered with no detail.' };\n  return { ok: true, detail: result.data };\n}\n\nexport type FetchPreviewResult = { ok: true; preview: FileEditsPreviewView } | { ok: false; error: string };\n\n/** One file the session never changed, read only; the route bounds it to the working directory. */\nexport async function fetchFilePreview(sessionId: string, filePath: string): Promise<FetchPreviewResult> {\n  const result = await api.session(sessionId).preview({ query: { [PATH_QUERY_PARAM]: filePath } });\n  if (!result.ok) return { ok: false, error: messageOf(result) };\n  if (!isRecord(result.data)) return { ok: false, error: 'The session answered with no file.' };\n  return { ok: true, preview: result.data };\n}\n\nexport type SaveResult =\n  | { ok: true; hash: string }\n  /** The file moved under the editor; `hash` is what it holds now. */\n  | { ok: false; stale: true; error: string; hash?: string }\n  | { ok: false; stale: false; error: string };\n\nexport async function saveFileContent(\n  sessionId: string,\n  filePath: string,\n  expectedHash: string,\n  content: string,\n): Promise<SaveResult> {\n  const result = await api.session(sessionId).save({ body: { path: filePath, expectedHash, content } });\n  // The agent runs in the same working directory, so it can rewrite the file\n  // while a reader is still editing it. A moved hash is the one refusal the\n  // editor recovers from, so it is read before the generic failure.\n  if (result.status === 409) {\n    const data = result.ok ? undefined : result.data;\n    const hash = isRecord(data) && typeof data.hash === 'string' ? data.hash : undefined;\n    return {\n      ok: false,\n      stale: true,\n      error: result.ok ? 'The file changed since it was opened.' : messageOf(result),\n      ...(hash === undefined ? {} : { hash }),\n    };\n  }\n  if (!result.ok) return { ok: false, stale: false, error: messageOf(result) };\n  return { ok: true, hash: isRecord(result.data) && typeof result.data.hash === 'string' ? result.data.hash : '' };\n}\n\nexport type DeleteResult = { ok: true } | { ok: false; error: string };\n\n/**\n * Removes the file from disk.\n *\n * The route takes only a path, with no hash to prove the reader saw what is\n * there now: a deletion discards the file whatever it holds, so a staleness\n * check would guard nothing. The confirmation in front of it is the guard.\n */\nexport async function deleteFile(sessionId: string, filePath: string): Promise<DeleteResult> {\n  const result = await api.session(sessionId).remove({ query: { [PATH_QUERY_PARAM]: filePath } });\n  return result.ok ? { ok: true } : { ok: false, error: messageOf(result) };\n}\n","import { grammarKeyOf, mediaKindOf } from '@agimon-ai/doompi-web-components';\n\nimport type { FileEditTool } from '../../../../../types/domain';\nimport type { FileEditsDiffHunk } from '../../../../../types/fileEditsApi';\nimport type { FilesItemView } from '../../../../../types/webFiles';\n\n/**\n * Pure view logic the file surfaces share: how a change is labelled, how a\n * file is identified, and how a set of notes becomes one message.\n *\n * None of this touches React or the DOM beyond what it is handed, so the\n * awkward parts (wording a review, sizing a gutter) are pinned by tests rather\n * than by clicking through a browser.\n */\n\n/** One review note anchored to source lines, or to a quoted rendered selection. */\nexport interface FileComment {\n  id: string;\n  path: string;\n  relPath: string;\n  startLine?: number;\n  endLine?: number;\n  snippet: string;\n  body: string;\n}\n\n/** What a row says about the tool behind a change. */\nexport const TOOL_LABEL: Readonly<Record<FileEditTool, string>> = {\n  edit: 'edit',\n  write: 'write',\n  bash: 'command',\n  user: 'you',\n};\n\n/** Filters visible relative paths without changing the timeline's newest-first order. */\nexport function filterFileItems(items: readonly FilesItemView[], query: string): FilesItemView[] {\n  const normalized = query.trim().toLocaleLowerCase();\n  if (normalized.length === 0) return [...items];\n  return items.filter((item) => item.relPath.toLocaleLowerCase().includes(normalized));\n}\n\n/** One header row and the files under it: the drawer's two-level grouping of a flat path list. */\nexport interface FileGroup {\n  /** The shared directory the header names, or empty for files at the repository root. */\n  prefix: string;\n  items: FilesItemView[];\n}\n\n/** The directory part of a relative path, or empty for a file at the root. */\nfunction parentDir(relPath: string): string {\n  const cut = relPath.lastIndexOf('/');\n  return cut === -1 ? '' : relPath.slice(0, cut);\n}\n\n/** The longest directory two paths share, compared segment by segment rather than character by character. */\nfunction commonDir(left: string, right: string): string {\n  const leftParts = left.split('/');\n  const rightParts = right.split('/');\n  const shared: string[] = [];\n  for (let index = 0; index < Math.min(leftParts.length, rightParts.length); index += 1) {\n    if (leftParts[index] !== rightParts[index]) break;\n    shared.push(leftParts[index]);\n  }\n  return shared.join('/');\n}\n\n/**\n * Groups a flat list into one header per top-level segment, each headed by the\n * deepest directory that group shares.\n *\n * Two levels rather than a full tree: paths here run five and six segments\n * deep, so a node per segment spends more rows on directories than on the\n * files someone opened the drawer to find. The header carries the shared run\n * of directories and each row carries what is left, so a file stays one row\n * and the part that distinguishes it stays readable.\n *\n * Ordering is by path, not by recency, because a header only means anything\n * when its files sit together underneath it. The caller marks the newest\n * change instead.\n */\nexport function groupFileItems(items: readonly FilesItemView[]): FileGroup[] {\n  const buckets = new Map<string, FilesItemView[]>();\n  for (const item of items) {\n    const cut = item.relPath.indexOf('/');\n    const key = cut === -1 ? '' : item.relPath.slice(0, cut);\n    const bucket = buckets.get(key);\n    if (bucket === undefined) buckets.set(key, [item]);\n    else bucket.push(item);\n  }\n  const groups: FileGroup[] = [];\n  for (const [key, bucket] of buckets) {\n    const sorted = [...bucket].sort((left, right) => left.relPath.localeCompare(right.relPath));\n    // Root files have no directory to name, so they keep an empty header and\n    // render their own paths in full.\n    const prefix = key === '' ? '' : sorted.map((item) => parentDir(item.relPath)).reduce(commonDir);\n    groups.push({ prefix, items: sorted });\n  }\n  return groups.sort((left, right) => left.prefix.localeCompare(right.prefix));\n}\n\n/** What one row shows once its group's header has already said the shared part. */\nexport function groupRowLabel(prefix: string, relPath: string): string {\n  return prefix === '' ? relPath : relPath.slice(prefix.length + 1);\n}\n\n/**\n * How the preview shows a file: as the thing it is, wherever the browser can.\n *\n * One value rather than a chain of checks at the call site, because the order\n * carries the decisions. Media comes first, so a PNG the snapshot store\n * refused as binary is still a picture rather than an apology. `unavailable`\n * comes next, since a file with no readable text has nothing for the rest of\n * the list to render. Then the two documents that have a rendering of their\n * own, and then code, which is shown highlighted rather than as flat text.\n */\nexport type PreviewMode = 'media' | 'unavailable' | 'markdown' | 'html' | 'code' | 'text';\n\nexport function previewModeOf(filePath: string, unavailable: boolean): PreviewMode {\n  if (mediaKindOf(filePath) !== 'download') return 'media';\n  if (unavailable) return 'unavailable';\n  const lower = filePath.toLowerCase();\n  if (lower.endsWith('.md') || lower.endsWith('.markdown')) return 'markdown';\n  if (lower.endsWith('.html') || lower.endsWith('.htm')) return 'html';\n  return grammarKeyOf(filePath) === undefined ? 'text' : 'code';\n}\n\n/** A short, stable fingerprint of a string; enough to separate two paths in an id. */\nfunction fingerprint(value: string): string {\n  let hash = 2166136261;\n  for (let index = 0; index < value.length; index += 1) {\n    hash ^= value.charCodeAt(index);\n    hash = Math.imul(hash, 16777619);\n  }\n  return (hash >>> 0).toString(36);\n}\n\n/** The transient tab id for one file; stable, so reopening focuses rather than duplicates. */\nexport function fileTabId(filePath: string): string {\n  // The id reaches a URL, so anything outside the safe set becomes a dash, and\n  // the path's own fingerprint keeps two similarly-named files apart.\n  const slug = filePath.replaceAll(/[^a-zA-Z0-9]+/gu, '-').replace(/^-|-$/gu, '');\n  return `files-file-${fingerprint(filePath)}-${slug.slice(-40)}`;\n}\n\n/** The widest line number in a diff, in characters, which sizes the gutter. */\nexport function gutterWidth(hunks: readonly FileEditsDiffHunk[]): number {\n  let widest = 1;\n  for (const hunk of hunks) {\n    for (const row of hunk.rows) widest = Math.max(widest, String(row.line).length);\n  }\n  return widest;\n}\n\n/** Trims a quoted snippet to something a prompt can carry without drowning it. */\nexport function trimSnippet(snippet: string, maxLines = 20, maxChars = 2000): string {\n  const lines = snippet.split('\\n');\n  const clipped = lines.length > maxLines ? [...lines.slice(0, maxLines), '…'] : lines;\n  const text = clipped.join('\\n');\n  return text.length > maxChars ? `${text.slice(0, maxChars)}…` : text;\n}\n\n/** How a comment names the place it is about. */\nexport function commentAnchor(comment: FileComment): string {\n  if (comment.startLine === undefined) return comment.relPath;\n  if (comment.endLine === undefined || comment.endLine === comment.startLine) {\n    return `${comment.relPath}:${comment.startLine}`;\n  }\n  return `${comment.relPath}:${comment.startLine}-${comment.endLine}`;\n}\n\n/**\n * The one message a review sends.\n *\n * Every note goes in a single prompt rather than one message each: N notes then\n * cost one turn, and the agent sees the whole review before it changes\n * anything, which is the difference between addressing a review and reacting to\n * its first line.\n */\nexport function buildReviewPrompt(comments: readonly FileComment[]): string {\n  if (comments.length === 0) return '';\n  const heading =\n    comments.length === 1\n      ? 'I left one review comment on a file you changed. Please address it.'\n      : `I left ${comments.length} review comments on files you changed. Please address them.`;\n  const body = comments\n    .map((comment) => {\n      const quoted = trimSnippet(comment.snippet)\n        .split('\\n')\n        .map((line) => `> ${line}`)\n        .join('\\n');\n      return `### ${commentAnchor(comment)}\\n\\n${quoted}\\n\\n${comment.body.trim()}`;\n    })\n    .join('\\n\\n');\n  return `${heading}\\n\\n${body}`;\n}\n","import { Button, Textarea } from '@agimon-ai/doompi-web-components';\nimport { useState } from 'react';\n\nimport { trimSnippet } from '../_lib/fileView';\n\n/**\n * The box a selection raises: what was highlighted, and a note about it.\n *\n * The note is held here rather than in the session store because it belongs to\n * one open box and dies with it; only a submitted comment is worth surviving a\n * re-render, and that goes to the store.\n */\nexport interface CommentDraftProps {\n  snippet: string;\n  /** Absent when the selection came from a rendered preview, where lines cannot be recovered. */\n  startLine?: number;\n  endLine?: number;\n  onSubmit: (body: string) => void;\n  onCancel: () => void;\n}\n\nexport function CommentDraft({ snippet, startLine, endLine, onSubmit, onCancel }: CommentDraftProps) {\n  const [body, setBody] = useState('');\n  const range =\n    startLine === undefined\n      ? 'no line anchor: this selection came from the rendered preview'\n      : endLine === undefined || endLine === startLine\n        ? `line ${startLine}`\n        : `lines ${startLine} to ${endLine}`;\n\n  return (\n    <div data-testid=\"files-comment-draft\" className=\"flex flex-col gap-1.5 border-t border-doom-border p-2\">\n      <p className=\"text-2xs text-doom-faint\">{range}</p>\n      <pre className=\"max-h-24 overflow-y-auto whitespace-pre-wrap break-words rounded border border-doom-border-soft bg-doom-deep p-1.5 font-mono text-xs text-doom-dim\">\n        {trimSnippet(snippet, 8, 600)}\n      </pre>\n      <Textarea\n        data-testid=\"files-comment-body\"\n        value={body}\n        rows={3}\n        autoFocus\n        placeholder=\"what should change here?\"\n        onChange={(event) => setBody(event.target.value)}\n        onKeyDown={(event) => {\n          if (event.key === 'Escape') onCancel();\n          if (event.key !== 'Enter' || !(event.metaKey || event.ctrlKey)) return;\n          event.preventDefault();\n          if (body.trim() !== '') onSubmit(body);\n        }}\n        className=\"text-sm\"\n      />\n      <div className=\"flex items-center gap-1.5\">\n        <Button\n          variant=\"outline\"\n          size=\"xs\"\n          data-testid=\"files-comment-add\"\n          disabled={body.trim() === ''}\n          onClick={() => onSubmit(body)}\n          className=\"text-2xs\"\n        >\n          add comment\n        </Button>\n        <Button variant=\"ghost\" size=\"xs\" data-testid=\"files-comment-cancel\" onClick={onCancel} className=\"text-2xs\">\n          cancel\n        </Button>\n        <span className=\"text-2xs text-doom-faint\">⌘⏎ to add</span>\n      </div>\n    </div>\n  );\n}\n","import {\n  Button,\n  Dialog,\n  DialogBody,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from '@agimon-ai/doompi-web-components';\n\n/**\n * The confirmation that stands between the delete button and the unlink.\n *\n * Deleting is the one action here with nothing behind it: a manual save can be\n * undone from the file's own history, and a comment can be removed before it\n * is sent, but this session's snapshots go when the session does, so a file\n * removed by mistake is not coming back from anything the cockpit holds.\n */\nexport interface DeleteFileDialogProps {\n  relPath: string;\n  open: boolean;\n  onConfirm: () => void;\n  onCancel: () => void;\n}\n\nexport function DeleteFileDialog({ relPath, open, onConfirm, onCancel }: DeleteFileDialogProps) {\n  return (\n    <Dialog\n      open={open}\n      onOpenChange={(next) => {\n        if (!next) onCancel();\n      }}\n    >\n      <DialogContent width=\"sm\" data-testid=\"files-delete-dialog\" aria-describedby={undefined}>\n        <DialogHeader>\n          <DialogTitle>delete file</DialogTitle>\n        </DialogHeader>\n        <DialogBody>\n          <DialogDescription>\n            this removes <span className=\"font-bold text-doom-hi\">{relPath}</span> from disk. the session keeps its\n            record of the change, but the file itself is gone.\n          </DialogDescription>\n          <DialogFooter>\n            <Button variant=\"outline\" size=\"md\" data-testid=\"files-delete-cancel\" autoFocus onClick={onCancel}>\n              cancel\n            </Button>\n            <Button variant=\"danger\" size=\"md\" data-testid=\"files-delete-confirm\" onClick={onConfirm}>\n              delete\n            </Button>\n          </DialogFooter>\n        </DialogBody>\n      </DialogContent>\n    </Dialog>\n  );\n}\n","import type { FileEditsDiffHunk } from '../../../../../types/fileEditsApi';\nimport { gutterWidth } from '../_lib/fileView';\n\n/**\n * A diff, drawn the way this cockpit draws every other one: a numbered gutter\n * and a background wash per row, so a change reads at a glance rather than by\n * hunting for leading punctuation.\n *\n * Hunks arrive apart rather than joined by an elision marker, so the gap\n * between two of them is a rule the component draws, not a line it has to\n * recognise in a string.\n */\n\nconst ROW_TONE: Readonly<Record<string, string>> = {\n  '+': 'bg-doom-tint-green text-doom-green',\n  '-': 'bg-doom-tint-red text-doom-red',\n  ' ': 'text-doom-dim',\n};\n\nexport interface DiffViewProps {\n  hunks: readonly FileEditsDiffHunk[];\n  /** Marks the surface so a test and a selection handler can find it. */\n  testId: string;\n  /** Called with the selected text and the lines it covers. */\n  onSelect?: (selection: { startLine: number; endLine: number; snippet: string }) => void;\n}\n\nexport function DiffView({ hunks, testId, onSelect }: DiffViewProps) {\n  const gutter = gutterWidth(hunks);\n\n  /**\n   * Turns whatever the reader highlighted into a line range. The rows carry\n   * their own numbers, so the range comes from the first and last row the\n   * selection touches rather than from counting newlines in the rendered text.\n   */\n  const handleSelect = (): void => {\n    if (onSelect === undefined) return;\n    const selection = globalThis.getSelection?.();\n    const text = selection?.toString() ?? '';\n    if (text.trim() === '') return;\n    const rows = [...document.querySelectorAll<HTMLElement>(`[data-diff-surface=\"${testId}\"] [data-diff-line]`)];\n    const touched = rows.filter((row) => selection?.containsNode(row, true) === true);\n    const numbers = touched\n      .map((row) => Number(row.dataset.diffLine))\n      .filter((line) => Number.isFinite(line) && line > 0);\n    if (numbers.length === 0) return;\n    onSelect({ startLine: Math.min(...numbers), endLine: Math.max(...numbers), snippet: text });\n  };\n\n  if (hunks.length === 0) {\n    return (\n      <p data-testid={`${testId}-empty`} className=\"px-2 py-1 text-xs text-doom-faint\">\n        no lines changed\n      </p>\n    );\n  }\n\n  return (\n    <div\n      data-testid={testId}\n      data-diff-surface={testId}\n      onMouseUp={handleSelect}\n      className=\"overflow-x-auto font-mono text-sm leading-normal\"\n    >\n      {hunks.map((hunk, position) => (\n        <div key={`${hunk.start}-${position}`}>\n          {position > 0 ? (\n            <div className=\"flex items-center gap-2 px-2 py-0.5 text-2xs text-doom-faint\">\n              <span className=\"h-px flex-1 bg-doom-border-soft\" />\n              <span>⋯</span>\n              <span className=\"h-px flex-1 bg-doom-border-soft\" />\n            </div>\n          ) : null}\n          {hunk.rows.map((row, offset) => (\n            <div\n              key={`${row.marker}-${row.line}-${offset}`}\n              data-diff-line={row.line}\n              data-diff-marker={row.marker}\n              className={`flex whitespace-pre ${ROW_TONE[row.marker] ?? 'text-doom-dim'}`}\n            >\n              <span\n                aria-hidden\n                className=\"shrink-0 select-none pr-2 pl-2 text-right text-doom-faint\"\n                style={{ width: `${gutter + 2}ch` }}\n              >\n                {row.line}\n              </span>\n              <span className=\"shrink-0 select-none pr-1\">{row.marker}</span>\n              <span className=\"min-w-0\">{row.content}</span>\n            </div>\n          ))}\n        </div>\n      ))}\n    </div>\n  );\n}\n","import { MediaPreview } from '@agimon-ai/doompi-web-components';\nimport { sealedTransport } from '@agimon-ai/doompi-web-security/browser';\nimport { useEffect, useState } from 'react';\n\n/** Media elements cannot issue the encrypted fetch required by remote session files. */\nexport function SessionMediaPreview({\n  src,\n  path,\n  'data-testid': testId,\n}: {\n  src: string;\n  path: string;\n  'data-testid'?: string;\n}) {\n  const [loaded, setLoaded] = useState<{ source: string; url?: string; error?: string }>();\n  useEffect(() => {\n    if (src === '') return;\n    const controller = new AbortController();\n    let url: string | undefined;\n    void (async () => {\n      const response = await sealedTransport.fetch(src, { signal: controller.signal, cache: 'no-store' });\n      if (!response.ok) throw new Error(`Media could not be loaded (${response.status}).`);\n      const blob = await response.blob();\n      if (controller.signal.aborted) return;\n      url = URL.createObjectURL(blob);\n      setLoaded({ source: src, url });\n    })().catch((error: unknown) => {\n      if (!controller.signal.aborted)\n        setLoaded({ source: src, error: error instanceof Error ? error.message : String(error) });\n    });\n    return () => {\n      controller.abort();\n      if (url !== undefined) URL.revokeObjectURL(url);\n    };\n  }, [src]);\n  const current = loaded?.source === src ? loaded : undefined;\n  if (current?.url === undefined)\n    return <p role={current?.error ? 'alert' : 'status'}>{current?.error ?? 'Loading media...'}</p>;\n  return <MediaPreview src={current.url} path={path} data-testid={testId} />;\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport {\n  Badge,\n  Breadcrumb,\n  Button,\n  CodeEditor,\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n  type EditorSelectionRange,\n  KebabIcon,\n  Markdown,\n} from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useEffect, useRef, useState } from 'react';\n\nimport type { FileEditsVersionView } from '../../../../../types/fileEditsApi';\nimport { deleteFile, fetchFileDetail, saveFileContent, sessionFileUrl } from '../_lib/filesApi';\nimport {\n  addComment,\n  clearComments,\n  files,\n  markLoading,\n  removeComment,\n  storeDetail,\n  storeError,\n} from '../_lib/filesStore';\nimport { buildReviewPrompt, commentAnchor, fileTabId, previewModeOf, TOOL_LABEL } from '../_lib/fileView';\nimport { CommentDraft } from './CommentDraft';\nimport { DeleteFileDialog } from './DeleteFileDialog';\nimport { DiffView } from './DiffView';\nimport { SessionMediaPreview as MediaPreview } from './SessionMediaPreview';\n\n/**\n * One file's tab: what this session did to it, and what the reader wants done\n * next.\n *\n * Three views over the same file. The diff answers \"what changed\", the history\n * below it answers \"in what order\", and edit is where a reader fixes a line\n * themselves rather than asking for it. Preview exists because a changed\n * README is read, not diffed, and because a changed PDF or screenshot has no\n * text to read at all.\n *\n * The tab is transient: it belongs to the session and the page, and closing it\n * is how it ends. Its cached detail lives in the session store, so reopening\n * the same file does not refetch a history the session already answered.\n */\n\n/**\n * `preview` and `diff` are the two ways of reading the file and sit in the\n * header. `edit` is a mode entered from the menu and left by picking either\n * reading view; the draft survives the trip, so leaving to check a diff does\n * not throw away what was typed.\n */\ntype View = 'preview' | 'diff' | 'edit';\n\ninterface FilePanelProps extends WebPluginSlotProps {\n  filePath: string;\n  relPath: string;\n}\n\ninterface Draft {\n  snippet: string;\n  startLine?: number;\n  endLine?: number;\n}\n\n/** How a version heads its row: what ran, when, and what it moved. */\nfunction versionSummary(version: FileEditsVersionView): string {\n  const time = new Date(version.at).toLocaleTimeString();\n  const counts = version.hunks === undefined ? '' : ` +${version.additions} -${version.removals}`;\n  return `${time} · ${TOOL_LABEL[version.tool] ?? version.tool}${counts}`;\n}\n\nexport function FilePanel({ filePath, relPath, sessionId, sendSessionFrame, closeTransientTab }: FilePanelProps) {\n  const session = useStore(files.store, (state) => files.select(state, sessionId));\n  const detail = session.detail[filePath];\n  const error = session.errors[filePath];\n  const loading = session.loading.includes(filePath);\n  const comments = session.comments.filter((comment) => comment.path === filePath);\n\n  // Preview first: clicking a file is a request to look at the file, and the\n  // diff is one question about it. The diff is one click away and the counts\n  // beside the toggles already say whether there is one worth opening.\n  const [view, setView] = useState<View>('preview');\n  const [draft, setDraft] = useState<Draft | undefined>(undefined);\n  const [source, setSource] = useState<string | undefined>(undefined);\n  const [expanded, setExpanded] = useState<number | undefined>(undefined);\n  const [saving, setSaving] = useState(false);\n  const [saveNote, setSaveNote] = useState<string | undefined>(undefined);\n  const [confirmingDelete, setConfirmingDelete] = useState(false);\n  // The editor reports a range on every cursor move, and the comment box takes\n  // focus when it opens. Holding the latest range here and raising the box on\n  // mouse or key release keeps a drag from stealing focus halfway through it,\n  // and a ref rather than state keeps a cursor move from re-rendering the tab.\n  const selection = useRef<EditorSelectionRange | undefined>(undefined);\n\n  // One fetch per open, and again whenever the session reports the file moved:\n  // the count changing is the cheapest signal that the history grew.\n  const revision = session.items.find((item) => item.path === filePath)?.count ?? 0;\n  useEffect(() => {\n    if (sessionId === null) return;\n    let cancelled = false;\n    markLoading(sessionId, filePath);\n    void fetchFileDetail(sessionId, filePath).then((result) => {\n      if (cancelled) return;\n      if (result.ok) {\n        storeDetail(sessionId, result.detail);\n        setSource(undefined);\n        setSaveNote(undefined);\n      } else storeError(sessionId, filePath, result.error);\n    });\n    return () => {\n      cancelled = true;\n    };\n  }, [sessionId, filePath, revision]);\n\n  const working = detail?.working;\n  const content = source ?? working?.content ?? '';\n  const previewMode = previewModeOf(relPath, working?.unavailable === true);\n  // An image, a video or a PDF is shown rather than read as text, and it is\n  // shown from the hub's own bytes route rather than from the detail payload,\n  // which carries text only.\n  const mediaSrc = sessionId === null ? '' : sessionFileUrl(sessionId, relPath);\n\n  const openDraft = (selection: Draft): void => {\n    setDraft(selection);\n  };\n\n  /** Raises the comment box on whatever the editor last reported, if that was a selection. */\n  const openSelectionDraft = (): void => {\n    const range = selection.current;\n    if (range === undefined || range.text.trim() === '') return;\n    openDraft({ snippet: range.text, startLine: range.startLine, endLine: range.endLine });\n  };\n\n  const submitDraft = (body: string): void => {\n    if (sessionId === null || draft === undefined) return;\n    addComment(sessionId, {\n      // The anchor plus the note's position is unique within a file, and a\n      // comment never outlives the session that holds it.\n      id: `${filePath}:${draft.startLine ?? 0}:${comments.length}:${body.length}`,\n      path: filePath,\n      relPath,\n      ...(draft.startLine === undefined ? {} : { startLine: draft.startLine }),\n      ...(draft.endLine === undefined ? {} : { endLine: draft.endLine }),\n      snippet: draft.snippet,\n      body,\n    });\n    setDraft(undefined);\n  };\n\n  const sendReview = (): void => {\n    if (sessionId === null || comments.length === 0) return;\n    sendSessionFrame(sessionId, { type: 'prompt', message: buildReviewPrompt(comments) });\n    clearComments(sessionId, filePath);\n  };\n\n  const confirmDelete = async (): Promise<void> => {\n    if (sessionId === null) return;\n    setConfirmingDelete(false);\n    const result = await deleteFile(sessionId, filePath);\n    if (!result.ok) {\n      storeError(sessionId, filePath, result.error);\n      return;\n    }\n    // The file leaves the dock on the next channel frame, and this tab is now\n    // a view of something that is not there, so it goes with it.\n    closeTransientTab(fileTabId(filePath));\n  };\n\n  const save = async (): Promise<void> => {\n    if (sessionId === null || working === undefined || source === undefined) return;\n    setSaving(true);\n    const result = await saveFileContent(sessionId, filePath, working.hash, source);\n    setSaving(false);\n    if (result.ok) {\n      setSaveNote('saved');\n      setSource(undefined);\n      markLoading(sessionId, filePath);\n      const refreshed = await fetchFileDetail(sessionId, filePath);\n      if (refreshed.ok) storeDetail(sessionId, refreshed.detail);\n      else storeError(sessionId, filePath, refreshed.error);\n      return;\n    }\n    setSaveNote(result.error);\n  };\n\n  return (\n    <div data-testid=\"files-file-panel\" className=\"flex min-h-0 flex-1 flex-col\">\n      <header className=\"flex shrink-0 items-center gap-2 border-b border-doom-border px-4 py-2\">\n        <Breadcrumb path={relPath} data-testid=\"files-breadcrumb\" className=\"min-w-0 flex-1\" />\n        {/* Edit is not one of the header's two toggles, so with neither lit the\n            mode needs saying; picking either toggle leaves it. */}\n        {view === 'edit' ? (\n          <Badge tone=\"neutral\" data-testid=\"files-editing\" className=\"shrink-0 text-2xs\">\n            editing\n          </Badge>\n        ) : null}\n        {detail === undefined ? null : (\n          <span className=\"shrink-0 text-2xs text-doom-faint\">\n            +{detail.cumulative.additions} -{detail.cumulative.removals}\n          </span>\n        )}\n        {/* The two ways of looking at the file sit in the header; the things\n            that act on it live behind the kebab, so a destructive one is never\n            a mis-click away from a view toggle. */}\n        <nav className=\"flex shrink-0 items-center gap-1\">\n          {(['preview', 'diff'] as const).map((entry) => (\n            <Button\n              key={entry}\n              variant={view === entry ? 'outline' : 'ghost'}\n              size=\"xs\"\n              data-testid={`files-view-${entry}`}\n              onClick={() => setView(entry)}\n              className=\"text-2xs\"\n            >\n              {entry}\n            </Button>\n          ))}\n        </nav>\n        <DropdownMenu>\n          <DropdownMenuTrigger asChild>\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              data-testid=\"files-menu\"\n              title=\"file actions\"\n              className=\"shrink-0 text-doom-faint hover:text-doom-hi\"\n            >\n              <KebabIcon className=\"h-3 w-3\" />\n            </Button>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent data-testid=\"files-menu-list\">\n            <DropdownMenuItem\n              data-testid=\"files-edit\"\n              disabled={working?.unavailable === true}\n              onSelect={() => setView('edit')}\n            >\n              edit\n            </DropdownMenuItem>\n            <DropdownMenuItem\n              variant=\"destructive\"\n              data-testid=\"files-delete\"\n              disabled={working?.unavailable === true}\n              onSelect={() => setConfirmingDelete(true)}\n            >\n              delete\n            </DropdownMenuItem>\n            <DropdownMenuItem data-testid=\"files-close\" onSelect={() => closeTransientTab(fileTabId(filePath))}>\n              close\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </header>\n\n      <DeleteFileDialog\n        relPath={relPath}\n        open={confirmingDelete}\n        onConfirm={() => void confirmDelete()}\n        onCancel={() => setConfirmingDelete(false)}\n      />\n\n      {loading && detail === undefined ? (\n        <p data-testid=\"files-loading\" className=\"px-4 py-3 text-sm text-doom-faint\">\n          reading this file's history…\n        </p>\n      ) : null}\n      {error === undefined ? null : (\n        <p data-testid=\"files-error\" className=\"px-4 py-3 text-sm text-doom-red\">\n          {error}\n        </p>\n      )}\n\n      <div className=\"min-h-0 flex-1 overflow-y-auto\">\n        {detail === undefined ? null : view === 'diff' ? (\n          <div className=\"flex flex-col\">\n            {detail.cumulative.note === undefined ? null : (\n              <p data-testid=\"files-cumulative-note\" className=\"px-4 py-2 text-xs text-doom-yellow\">\n                {detail.cumulative.note}\n              </p>\n            )}\n            {detail.cumulative.hunks === undefined ? null : (\n              <DiffView\n                hunks={detail.cumulative.hunks}\n                testId=\"files-cumulative-diff\"\n                onSelect={(selection) => openDraft(selection)}\n              />\n            )}\n            <section className=\"mt-2 border-t border-doom-border\">\n              <h2 className=\"px-4 py-2 text-xs font-bold tracking-wide text-doom-faint uppercase\">\n                {detail.versions.length} {detail.versions.length === 1 ? 'change' : 'changes'} this session\n              </h2>\n              {detail.versions.map((version) => (\n                <article key={version.index} data-testid={`files-version-${version.index}`}>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"card\"\n                    data-testid={`files-version-toggle-${version.index}`}\n                    onClick={() => setExpanded(expanded === version.index ? undefined : version.index)}\n                    className=\"w-full gap-2 rounded-none px-4 py-1.5 hover:bg-doom-panel\"\n                  >\n                    <span className=\"flex w-full min-w-0 items-center gap-2\">\n                      <span className=\"shrink-0 text-2xs text-doom-faint\">#{version.index}</span>\n                      <span className=\"min-w-0 flex-1 truncate text-left text-xs text-doom-text\">\n                        {versionSummary(version)}\n                      </span>\n                      {version.origin === 'scan' ? (\n                        <Badge tone=\"neutral\" className=\"shrink-0 text-2xs\">\n                          no baseline\n                        </Badge>\n                      ) : null}\n                      <span className=\"shrink-0 text-2xs text-doom-faint\">\n                        {expanded === version.index ? '▾' : '▸'}\n                      </span>\n                    </span>\n                  </Button>\n                  {expanded !== version.index ? null : version.hunks === undefined ? (\n                    <p className=\"px-4 py-1.5 text-xs text-doom-faint\">{version.note ?? 'nothing to show'}</p>\n                  ) : (\n                    <DiffView\n                      hunks={version.hunks}\n                      testId={`files-version-diff-${version.index}`}\n                      onSelect={(selection) => openDraft(selection)}\n                    />\n                  )}\n                </article>\n              ))}\n            </section>\n          </div>\n        ) : view === 'edit' ? (\n          <div className=\"flex h-full flex-col gap-2 p-3\">\n            {working?.unavailable === true ? (\n              <p data-testid=\"files-source-unavailable\" className=\"text-sm text-doom-faint\">\n                {working.reason ?? 'this file cannot be shown'}\n              </p>\n            ) : (\n              <>\n                {/* The release, not the selection, is what raises the comment\n                    box: the editor reports a range continuously through a drag\n                    and the box takes focus when it opens. */}\n                <div\n                  className=\"min-h-[24rem] flex-1\"\n                  onMouseUp={openSelectionDraft}\n                  onKeyUp={(event) => {\n                    if (event.shiftKey) openSelectionDraft();\n                  }}\n                >\n                  <CodeEditor\n                    data-testid=\"files-source\"\n                    value={content}\n                    path={relPath}\n                    onChange={setSource}\n                    onSelect={(range) => {\n                      selection.current = range;\n                    }}\n                    className=\"h-full rounded border border-doom-border\"\n                  />\n                </div>\n                <div className=\"flex shrink-0 items-center gap-2\">\n                  <Button\n                    variant=\"outline\"\n                    size=\"xs\"\n                    data-testid=\"files-save\"\n                    loading={saving}\n                    disabled={source === undefined || saving}\n                    onClick={() => void save()}\n                    className=\"text-2xs\"\n                  >\n                    save to disk\n                  </Button>\n                  {source === undefined ? null : (\n                    <Button\n                      variant=\"ghost\"\n                      size=\"xs\"\n                      data-testid=\"files-revert\"\n                      onClick={() => setSource(undefined)}\n                      className=\"text-2xs\"\n                    >\n                      discard edits\n                    </Button>\n                  )}\n                  {saveNote === undefined ? null : (\n                    <span\n                      data-testid=\"files-save-note\"\n                      className={saveNote === 'saved' ? 'text-2xs text-doom-green' : 'text-2xs text-doom-red'}\n                    >\n                      {saveNote}\n                    </span>\n                  )}\n                </div>\n              </>\n            )}\n          </div>\n        ) : (\n          <div\n            data-testid=\"files-preview\"\n            data-mode={previewMode}\n            className={\n              previewMode === 'code'\n                ? 'flex h-full flex-col gap-2 p-4 text-sm text-doom-text'\n                : 'flex flex-col gap-2 p-4 text-sm text-doom-text'\n            }\n            onMouseUp={() => {\n              // Highlighted source knows which lines were picked, so a note on\n              // it is anchored. A rendered document has no honest mapping back\n              // to source lines, and a note there carries its quotation and\n              // says it has none.\n              if (previewMode === 'code') {\n                openSelectionDraft();\n                return;\n              }\n              const text = globalThis.getSelection?.()?.toString() ?? '';\n              if (text.trim() !== '') openDraft({ snippet: text });\n            }}\n          >\n            {/* A file the editor cannot hold is still a file the reader came\n                to see, so the reason is said and the bytes are shown anyway. */}\n            {previewMode === 'unavailable' ? (\n              <p data-testid=\"files-preview-unavailable\" className=\"text-sm text-doom-faint\">\n                {working?.reason ?? 'this file cannot be shown'}\n              </p>\n            ) : null}\n            {previewMode === 'media' || previewMode === 'unavailable' ? (\n              <MediaPreview src={mediaSrc} path={relPath} data-testid=\"files-preview-media\" />\n            ) : previewMode === 'markdown' ? (\n              <Markdown text={content} />\n            ) : previewMode === 'html' ? (\n              <iframe\n                data-testid=\"files-preview-html\"\n                title={relPath}\n                sandbox=\"\"\n                srcDoc={content}\n                className=\"h-[36rem] w-full rounded border border-doom-border bg-doom-deep\"\n              />\n            ) : previewMode === 'code' ? (\n              // The same editor the edit view uses, held open. Reading code as\n              // flat text is the one thing a cockpit should never make anyone\n              // do, and read-only keeps the two views honestly apart.\n              <CodeEditor\n                data-testid=\"files-preview-code\"\n                value={content}\n                path={relPath}\n                readOnly\n                onSelect={(range) => {\n                  selection.current = range;\n                }}\n                className=\"min-h-[24rem] flex-1 rounded border border-doom-border\"\n              />\n            ) : (\n              // Everything else is shown as it is written. Preview is the file\n              // as it stands, so a file with nothing to render still has a\n              // reading view rather than an empty pane.\n              <pre\n                data-testid=\"files-preview-text\"\n                className=\"whitespace-pre-wrap break-words font-mono text-sm leading-normal text-doom-text\"\n              >\n                {content}\n              </pre>\n            )}\n          </div>\n        )}\n      </div>\n\n      {draft === undefined ? null : (\n        <CommentDraft\n          snippet={draft.snippet}\n          {...(draft.startLine === undefined ? {} : { startLine: draft.startLine })}\n          {...(draft.endLine === undefined ? {} : { endLine: draft.endLine })}\n          onSubmit={submitDraft}\n          onCancel={() => setDraft(undefined)}\n        />\n      )}\n\n      {comments.length === 0 ? null : (\n        <footer data-testid=\"files-review\" className=\"shrink-0 border-t border-doom-border px-3 py-2\">\n          <div className=\"flex items-center gap-2 pb-1\">\n            <span className=\"flex-1 text-xs font-bold text-doom-hi\">\n              {comments.length} {comments.length === 1 ? 'comment' : 'comments'}\n            </span>\n            <Button\n              variant=\"outline\"\n              size=\"xs\"\n              data-testid=\"files-send-review\"\n              onClick={sendReview}\n              className=\"text-2xs\"\n            >\n              send review\n            </Button>\n          </div>\n          {comments.map((comment) => (\n            <div key={comment.id} data-testid=\"files-review-comment\" className=\"flex items-start gap-2 py-0.5\">\n              <span className=\"shrink-0 text-2xs text-doom-faint\">{commentAnchor(comment)}</span>\n              <span className=\"min-w-0 flex-1 truncate text-xs text-doom-text\">{comment.body}</span>\n              <Button\n                variant=\"ghost\"\n                size=\"xs\"\n                data-testid=\"files-review-remove\"\n                onClick={() => {\n                  if (sessionId !== null) removeComment(sessionId, comment.id);\n                }}\n                className=\"shrink-0 text-2xs\"\n              >\n                remove\n              </Button>\n            </div>\n          ))}\n        </footer>\n      )}\n    </div>\n  );\n}\n\n/** The transient tab one file opens in; the same file reopens rather than duplicating. */\nexport function fileTab(filePath: string, relPath: string): TransientTab {\n  return {\n    id: fileTabId(filePath),\n    label: relPath.split('/').at(-1) ?? relPath,\n    panel: (props) => <FilePanel {...props} filePath={filePath} relPath={relPath} />,\n  };\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { Breadcrumb, Button, CodeEditor, Markdown } from '@agimon-ai/doompi-web-components';\nimport { useEffect, useState } from 'react';\n\nimport type { FileEditsPreviewView } from '../../../../../../types/fileEditsApi';\nimport { SessionMediaPreview as MediaPreview } from '../../_components/SessionMediaPreview';\nimport { fetchFilePreview, sessionFileUrl } from '../../_lib/filesApi';\nimport { fileTabId, previewModeOf } from '../../_lib/fileView';\n\n/**\n * A file the session never changed, opened read-only.\n *\n * The tab a changed file opens is built around its history: a diff, a version\n * list, an editor, a delete. None of that exists for a file the session only\n * read, and offering an edit view over a file this package holds no baseline\n * for would put a save behind a reader who cannot see what they are about to\n * overwrite. So this is one view, the file as it stands, and nothing else.\n *\n * Its content comes from the preview route, which is bounded by the session's\n * working directory rather than by the timeline.\n */\n\ninterface FilePreviewPanelProps extends WebPluginSlotProps {\n  filePath: string;\n}\n\n/** What one fetch settled on, tagged with the file it was about. */\ninterface Loaded {\n  path: string;\n  preview?: FileEditsPreviewView;\n  error?: string;\n}\n\nexport function FilePreviewPanel({ filePath, sessionId, closeTransientTab }: FilePreviewPanelProps) {\n  // The answer carries the path it answered for, so switching files is a state\n  // that does not match rather than a reset the effect has to perform.\n  const [loaded, setLoaded] = useState<Loaded | undefined>(undefined);\n\n  useEffect(() => {\n    if (sessionId === null) return;\n    let cancelled = false;\n    void fetchFilePreview(sessionId, filePath).then((result) => {\n      if (cancelled) return;\n      setLoaded(result.ok ? { path: filePath, preview: result.preview } : { path: filePath, error: result.error });\n    });\n    return () => {\n      cancelled = true;\n    };\n  }, [sessionId, filePath]);\n\n  const current = loaded?.path === filePath ? loaded : undefined;\n  const preview = current?.preview;\n  const error = current?.error;\n  // Before the answer arrives the path is all there is to name the file by,\n  // and the route's own relative path replaces it once it does.\n  const relPath = preview?.relPath ?? filePath;\n  const working = preview?.working;\n  const content = working?.content ?? '';\n  const previewMode = previewModeOf(relPath, working?.unavailable === true);\n  const mediaSrc = sessionId === null ? '' : sessionFileUrl(sessionId, relPath);\n\n  return (\n    <div data-testid=\"files-preview-panel\" className=\"flex min-h-0 flex-1 flex-col\">\n      <header className=\"flex shrink-0 items-center gap-2 border-b border-doom-border px-4 py-2\">\n        <Breadcrumb path={relPath} data-testid=\"files-preview-breadcrumb\" className=\"min-w-0 flex-1\" />\n        <span className=\"shrink-0 text-2xs text-doom-faint\">unchanged</span>\n        <Button\n          variant=\"ghost\"\n          size=\"xs\"\n          data-testid=\"files-preview-close\"\n          onClick={() => closeTransientTab(filePreviewTabId(filePath))}\n          className=\"shrink-0 text-2xs\"\n        >\n          close\n        </Button>\n      </header>\n\n      {error === undefined ? null : (\n        <p data-testid=\"files-preview-error\" className=\"px-4 py-3 text-sm text-doom-red\">\n          {error}\n        </p>\n      )}\n      {preview === undefined && error === undefined ? (\n        <p data-testid=\"files-preview-loading\" className=\"px-4 py-3 text-sm text-doom-faint\">\n          reading this file…\n        </p>\n      ) : null}\n\n      {preview === undefined ? null : (\n        <div className=\"min-h-0 flex-1 overflow-y-auto\">\n          <div\n            data-testid=\"files-preview\"\n            data-mode={previewMode}\n            className=\"flex h-full flex-col gap-2 p-4 text-sm text-doom-text\"\n          >\n            {previewMode === 'unavailable' ? (\n              <p data-testid=\"files-preview-unavailable\" className=\"text-sm text-doom-faint\">\n                {working?.reason ?? 'this file cannot be shown'}\n              </p>\n            ) : null}\n            {previewMode === 'media' || previewMode === 'unavailable' ? (\n              <MediaPreview src={mediaSrc} path={relPath} data-testid=\"files-preview-media\" />\n            ) : previewMode === 'markdown' ? (\n              <Markdown text={content} />\n            ) : previewMode === 'html' ? (\n              <iframe\n                data-testid=\"files-preview-html\"\n                title={relPath}\n                sandbox=\"\"\n                srcDoc={content}\n                className=\"h-[36rem] w-full rounded border border-doom-border bg-doom-deep\"\n              />\n            ) : previewMode === 'code' ? (\n              <CodeEditor\n                data-testid=\"files-preview-code\"\n                value={content}\n                path={relPath}\n                readOnly\n                className=\"min-h-[24rem] flex-1 rounded border border-doom-border\"\n              />\n            ) : (\n              <pre\n                data-testid=\"files-preview-text\"\n                className=\"whitespace-pre-wrap break-words font-mono text-sm leading-normal text-doom-text\"\n              >\n                {content}\n              </pre>\n            )}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n\n/**\n * The id of the read-only tab, kept apart from the changed-file tab's own id:\n * the same file can be read now and changed later, and the two tabs are not\n * the same view of it.\n */\nexport function filePreviewTabId(filePath: string): string {\n  return `${fileTabId(filePath)}-preview`;\n}\n\n/** The transient tab an unchanged file opens in; the same file reopens rather than duplicating. */\nexport function filePreviewTab(filePath: string): TransientTab {\n  return {\n    id: filePreviewTabId(filePath),\n    label: filePath.split('/').at(-1) ?? filePath,\n    panel: (props) => <FilePreviewPanel {...props} filePath={filePath} />,\n  };\n}\n","import type { FileLinkSource, TransientTab } from '@agimon-ai/doompi-core/web';\n\nimport type { FilesItemView } from '../../../../../../types/webFiles';\nimport { fileTab } from '../../_components/FilePanel';\nimport { files } from '../../_lib/filesStore';\nimport { filePreviewTab } from '../_components/FilePreviewPanel';\n\n/**\n * The files a message names, as links into the same tab the activity dock\n * opens.\n *\n * Only files this session recorded a change to are claimed. A message quotes a\n * great deal that is shaped like nothing in particular, class names and globs\n * and flags among it, and guessing at what is a path produces links that open\n * an error. The recorded set is the one thing the page knows for certain, and\n * it is also the set a reader is most likely to want open.\n */\n\n/** A reference the way a message writes one: 'src/app.ts:42' or 'src/app.ts:42:9'. */\nconst LINE_SUFFIX = /:\\d+(?::\\d+)?$/;\n\nfunction itemsFor(sessionId: string | null): readonly FilesItemView[] {\n  return files.select(files.store.state, sessionId).items;\n}\n\nfunction match(sessionId: string | null, path: string): FilesItemView | undefined {\n  const candidate = path.trim().replace(LINE_SUFFIX, '');\n  if (candidate === '') return undefined;\n  return itemsFor(sessionId).find((item) => item.relPath === candidate || item.path === candidate);\n}\n\nexport const fileLinks: FileLinkSource = {\n  subscribe(listener: () => void) {\n    const subscription = files.store.subscribe(listener);\n    return () => subscription.unsubscribe();\n  },\n  // The paths themselves, not their count: a file that leaves the list while\n  // another arrives changes what resolves without changing how many do.\n  fingerprint: (sessionId) =>\n    itemsFor(sessionId)\n      .map((item) => item.relPath)\n      .join('\\n'),\n  resolve: (sessionId, path): TransientTab | undefined => {\n    const item = match(sessionId, path);\n    return item === undefined ? undefined : fileTab(item.path, item.relPath);\n  },\n  // A tool argument is a path on purpose, so nothing here has to be guessed.\n  // A file the session changed opens on its history; one it only read opens\n  // read-only, and the route behind that tab is what decides whether a path\n  // outside the working directory may be shown at all.\n  openPath: (sessionId, path): TransientTab | undefined => {\n    const item = match(sessionId, path);\n    if (item !== undefined) return fileTab(item.path, item.relPath);\n    // A call with no path argument at all hands this an empty string, and\n    // there is no file to open behind it.\n    const candidate = path.trim();\n    return candidate === '' ? undefined : filePreviewTab(candidate);\n  },\n};\n","import { defineFileLinks } from '@agimon-ai/doompi-core/web';\n\nimport { fileLinks } from './_lib/fileLinks';\nexport default defineFileLinks(fileLinks);\n","import { Button, CloseIcon, Input } from '@agimon-ai/doompi-web-components';\nimport type { ChangeEvent, KeyboardEvent as ReactKeyboardEvent } from 'react';\nimport { Fragment, useEffect, useRef, useState } from 'react';\n\nimport type { FilesItemView } from '../../../../../../types/webFiles';\nimport { filterFileItems, groupFileItems, groupRowLabel, TOOL_LABEL } from '../../_lib/fileView';\n\nfunction FileMetadata({ item, label, recent = false }: { item: FilesItemView; label?: string; recent?: boolean }) {\n  return (\n    <span className=\"flex min-w-0 w-full items-center gap-1.5\">\n      {/* The dock passes no label and keeps the whole path; the drawer's tree\n          passes what its group header has not already said. */}\n      <span className=\"min-w-0 flex-1 truncate text-left text-xs font-bold text-doom-hi\">{label ?? item.relPath}</span>\n      {recent ? (\n        <span data-recent=\"true\" title=\"most recent change\" className=\"shrink-0 text-2xs text-doom-blue\">\n          ●\n        </span>\n      ) : null}\n      {item.count > 1 ? <span className=\"shrink-0 text-2xs text-doom-faint\">{item.count}×</span> : null}\n      <span className={`shrink-0 text-2xs ${item.diffable ? 'text-doom-faint' : 'text-doom-yellow'}`}>\n        {TOOL_LABEL[item.tool] ?? item.tool}\n      </span>\n    </span>\n  );\n}\n\nexport function FileActivityRow({ item, onOpen }: { item: FilesItemView; onOpen: () => void }) {\n  return (\n    <Button\n      variant=\"ghost\"\n      size=\"card\"\n      data-testid={`activity-file-${item.relPath}`}\n      data-file-diffable={item.diffable}\n      title={item.diffable ? item.path : `${item.path} (changed by a command, so no diff was captured)`}\n      onClick={onOpen}\n      className=\"min-w-0 gap-0.5 rounded-md px-1 py-1 hover:bg-doom-panel\"\n    >\n      <FileMetadata item={item} />\n    </Button>\n  );\n}\n\n/**\n * The directory a run of rows shares.\n *\n * Deliberately not an option: the cursor counts files, and a header is a label\n * rather than something to open. Keeping it out of the listbox is what lets\n * arrow keys and Enter stay exactly what they were before the tree existed.\n */\nfunction FileGroupHeader({ prefix }: { prefix: string }) {\n  return (\n    <div\n      data-testid={`files-browser-group-${prefix}`}\n      className=\"truncate px-4 pt-2 pb-1 text-2xs font-bold tracking-wide text-doom-faint\"\n    >\n      {prefix}\n    </div>\n  );\n}\n\nfunction BrowserFileRow({\n  item,\n  label,\n  indented = false,\n  recent = false,\n  selected,\n  onSelect,\n  onOpen,\n}: {\n  item: FilesItemView;\n  label: string;\n  indented?: boolean;\n  recent?: boolean;\n  selected: boolean;\n  onSelect: () => void;\n  onOpen: () => void;\n}) {\n  return (\n    <div\n      role=\"option\"\n      aria-selected={selected}\n      tabIndex={-1}\n      data-testid={`files-browser-file-${item.relPath}`}\n      data-file-diffable={item.diffable}\n      data-selected={selected}\n      title={item.diffable ? item.path : `${item.path} (changed by a command, so no diff was captured)`}\n      onMouseEnter={onSelect}\n      onClick={onOpen}\n      className={`flex min-w-0 cursor-pointer border-l-2 py-2 ${indented ? 'pr-4 pl-8' : 'px-4'} ${\n        selected ? 'border-doom-blue bg-doom-panel' : 'border-transparent hover:bg-doom-panel/60'\n      }`}\n    >\n      <FileMetadata item={item} label={label} recent={recent} />\n    </div>\n  );\n}\n\n/** The complete session file picker, ordered exactly as the file timeline supplies it. */\nexport function FilesBrowser({\n  items,\n  onClose,\n  onOpen,\n}: {\n  items: readonly FilesItemView[];\n  onClose: () => void;\n  onOpen: (item: FilesItemView) => void;\n}) {\n  const [query, setQuery] = useState('');\n  const [selectedIndex, setSelectedIndex] = useState(0);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const listRef = useRef<HTMLDivElement>(null);\n  const shown = filterFileItems(items, query);\n  // Grouping only earns its headers with the whole list in view. While a query\n  // is active the reader is pinpointing one path, so the flat list answers that\n  // better than a shape wrapped around two matches.\n  const grouped = query.trim() === '' ? groupFileItems(shown) : undefined;\n  // The order rows actually appear in, which is what the cursor counts through.\n  const ordered = grouped === undefined ? shown : grouped.flatMap((group) => group.items);\n  const positions = new Map(ordered.map((item, index) => [item.path, index]));\n  const selected = ordered[selectedIndex];\n  // The timeline hands the newest change first, so the head of the unfiltered\n  // list is the one row worth marking once the tree has reordered by path.\n  const recentPath = items[0]?.path;\n\n  // A shrinking filter can strand the cursor past the end, so the clamp happens\n  // while rendering the shorter list rather than in a pass after it paints.\n  const [lastShownLength, setLastShownLength] = useState(shown.length);\n  if (lastShownLength !== shown.length) {\n    setLastShownLength(shown.length);\n    setSelectedIndex((current) => Math.min(current, Math.max(0, shown.length - 1)));\n  }\n\n  useEffect(() => {\n    listRef.current?.querySelector('[aria-selected=\"true\"]')?.scrollIntoView({ block: 'nearest' });\n  }, [selectedIndex]);\n\n  const move = (delta: number): void => {\n    if (ordered.length === 0) return;\n    setSelectedIndex((current) => (current + delta + ordered.length) % ordered.length);\n  };\n\n  const onKeyDown = (event: ReactKeyboardEvent<HTMLElement>): void => {\n    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n      event.preventDefault();\n      move(event.key === 'ArrowDown' ? 1 : -1);\n      return;\n    }\n    if (event.key === 'Escape') {\n      event.preventDefault();\n      onClose();\n      return;\n    }\n    if (\n      event.key === 'Enter' &&\n      selected !== undefined &&\n      (!(event.target instanceof Element) || event.target.closest('button') === null)\n    ) {\n      event.preventDefault();\n      onOpen(selected);\n    }\n  };\n\n  return (\n    <aside\n      data-testid=\"files-browser\"\n      aria-label=\"changed files\"\n      tabIndex={-1}\n      onKeyDown={onKeyDown}\n      className=\"fixed inset-y-0 right-0 z-50 flex w-[min(440px,calc(100vw-24px))] flex-col overflow-hidden border-l border-doom-border bg-doom-rail outline-none\"\n    >\n      <div className=\"flex h-11 shrink-0 items-center gap-2.5 border-b border-doom-border px-4\">\n        <span className=\"text-base font-bold text-doom-hi\"># files</span>\n        <span data-testid=\"files-browser-total\" className=\"text-2xs text-doom-faint\">\n          {items.length} changed\n        </span>\n        <span className=\"min-w-0 flex-1\" />\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          data-testid=\"files-browser-close\"\n          aria-label=\"close changed files\"\n          title=\"close changed files\"\n          onClick={onClose}\n        >\n          <CloseIcon className=\"h-3 w-3\" />\n        </Button>\n      </div>\n      <div className=\"flex items-center gap-2 px-4 pt-2.5 pb-1.5\">\n        <Input\n          ref={inputRef}\n          data-testid=\"files-browser-search\"\n          aria-label=\"search changed file paths\"\n          value={query}\n          placeholder=\"search paths…\"\n          autoFocus\n          onChange={(event: ChangeEvent<HTMLInputElement>) => {\n            setQuery(event.target.value);\n            setSelectedIndex(0);\n          }}\n        />\n        {query.length > 0 ? (\n          <Button\n            variant=\"ghost\"\n            size=\"xs\"\n            data-testid=\"files-browser-clear\"\n            aria-label=\"clear path search\"\n            onClick={() => {\n              setQuery('');\n              setSelectedIndex(0);\n              inputRef.current?.focus();\n            }}\n          >\n            clear\n          </Button>\n        ) : null}\n      </div>\n      <p data-testid=\"files-browser-matches\" className=\"shrink-0 px-4 pb-2 text-2xs text-doom-faint\">\n        {shown.length} matches · {grouped === undefined ? 'newest change first' : 'by path'}\n      </p>\n      <div\n        ref={listRef}\n        role=\"listbox\"\n        aria-label=\"changed files\"\n        className=\"flex min-h-0 flex-1 flex-col overflow-y-auto pb-2\"\n      >\n        {shown.length === 0 ? <p className=\"px-4 py-3 text-xs text-doom-faint\">nothing matches the path</p> : null}\n        {grouped === undefined\n          ? shown.map((item) => (\n              <BrowserFileRow\n                key={item.path}\n                item={item}\n                label={item.relPath}\n                recent={item.path === recentPath}\n                selected={positions.get(item.path) === selectedIndex}\n                onSelect={() => setSelectedIndex(positions.get(item.path) ?? 0)}\n                onOpen={() => onOpen(item)}\n              />\n            ))\n          : grouped.map((group) => (\n              <Fragment key={group.prefix}>\n                {group.prefix === '' ? null : <FileGroupHeader prefix={group.prefix} />}\n                {group.items.map((item) => (\n                  <BrowserFileRow\n                    key={item.path}\n                    item={item}\n                    label={groupRowLabel(group.prefix, item.relPath)}\n                    indented\n                    recent={item.path === recentPath}\n                    selected={positions.get(item.path) === selectedIndex}\n                    onSelect={() => setSelectedIndex(positions.get(item.path) ?? 0)}\n                    onOpen={() => onOpen(item)}\n                  />\n                ))}\n              </Fragment>\n            ))}\n      </div>\n      <div className=\"flex h-8 shrink-0 items-center border-t border-doom-border-soft bg-doom-deep px-4\">\n        <span className=\"text-2xs text-doom-faint\">↑↓ choose · enter open · esc close</span>\n      </div>\n    </aside>\n  );\n}\n","import type { WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { Button } from '@agimon-ai/doompi-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useState } from 'react';\n\nimport type { FilesItemView } from '../../../../../../types/webFiles';\nimport { fileTab } from '../../_components/FilePanel';\nimport { files } from '../../_lib/filesStore';\nimport { FileActivityRow, FilesBrowser } from './FilesBrowser';\n\n/**\n * The files group's body in the activity dock: what this session has changed.\n *\n * Every recorded file is listed, not only the ones the agent named. A file\n * found by comparing the tree around a bash call is marked, because it is\n * openable and editable but has no baseline to diff against, and a row that\n * hid that distinction would promise a diff the tab cannot show.\n */\n\n/** The most recent changes first, which is the order the timeline already folds to. */\nconst VISIBLE_ROWS = 5;\n\nexport function FilesActivitySection({ sessionId, openTransientTab }: WebPluginSlotProps) {\n  const { items } = useStore(files.store, (state) => files.select(state, sessionId));\n  const [browserOpen, setBrowserOpen] = useState(false);\n\n  const openFile = (item: FilesItemView): void => {\n    if (sessionId === null) return;\n    openTransientTab(fileTab(item.path, item.relPath));\n    setBrowserOpen(false);\n  };\n  if (items.length === 0) {\n    return (\n      <p data-testid=\"activity-summary-files\" className=\"px-1 text-xs text-doom-faint\">\n        nothing changed yet\n      </p>\n    );\n  }\n\n  const visible = items.slice(0, VISIBLE_ROWS);\n\n  return (\n    <div data-testid=\"activity-file-edits\" className=\"flex flex-col gap-0.5\">\n      {visible.map((item: FilesItemView) => (\n        <FileActivityRow key={item.path} item={item} onOpen={() => openFile(item)} />\n      ))}\n      {items.length > visible.length ? (\n        <Button\n          variant=\"link\"\n          size=\"xs\"\n          data-testid=\"activity-files-show-all\"\n          aria-label={`show all ${items.length} changed files`}\n          className=\"self-start px-1 pt-0.5 text-2xs text-doom-faint\"\n          onClick={() => setBrowserOpen(true)}\n        >\n          show all {items.length} files\n        </Button>\n      ) : null}\n      {browserOpen ? <FilesBrowser items={items} onClose={() => setBrowserOpen(false)} onOpen={openFile} /> : null}\n    </div>\n  );\n}\n","import { defineFill } from '@agimon-ai/doompi-core/web';\n\nimport { FilesActivitySection } from './_components/FilesActivitySection';\nexport default defineFill({ component: FilesActivitySection });\n","import { defineLeaderBinding } from '@agimon-ai/doompi-core/web';\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', detail: 'tools, skills and config' };\nexport default defineLeaderBinding({\n  path: [EXTENSION_GROUP, { key: 'f', label: 'files', detail: 'files this session changed' }],\n  command: 'file-edits',\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/doompi-core/web';\n\nimport activityGroupFiles from '../src/extensions/workspaces/sessions/(frontend)/activity-group/files.web';\nimport channelFileEdits from '../src/extensions/workspaces/sessions/(frontend)/channel/file-edits.web';\nimport fileLinksFiles from '../src/extensions/workspaces/sessions/(frontend)/file-links/files.web';\nimport fillFiles from '../src/extensions/workspaces/sessions/(frontend)/fill/Files.activity.files.web';\nimport leaderFiles from '../src/extensions/workspaces/sessions/(frontend)/leader/files.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n  (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n  id: 'files',\n  session: {\n    get activityGroups(): WebPluginContributions['activityGroups'] { return [via({ name: 'files' }, activityGroupFiles)]; },\n    get channels(): WebPluginContributions['channels'] { return [via({ channel: 'file_edits' }, channelFileEdits)]; },\n    get fileLinks(): WebPluginContributions['fileLinks'] { return fileLinksFiles; },\n    get fills(): WebPluginContributions['fills'] { return [via({ slot: 'activity.files', id: 'files' }, fillFiles)]; },\n    get leaderBindings(): WebPluginContributions['leaderBindings'] { return [via({ id: 'files.files' }, leaderFiles)]; },\n  },\n});\n"],"mappings":"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"}