diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml index a85d782857..5892d118ec 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md -2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: b7df5403ea11ff8ba32be0f9bede5a32d5bcd6ee -2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 8a45d452bfd98c1fe87a9a42bd4930273d034ca8 +2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: 887a3666501a97e0930fe7eb4282603f537c17e8 +2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 0202be5c22c75ff4feec225cdff12b772d64d1cf diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md index b7df5403ea..887a366650 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md @@ -16,6 +16,8 @@ Maintained prose names the exact actor, action, source, event, field, file, or p The rule applies to Markdown, READMEs, active Agent Notes, JSDoc and comments, prompts, diagnostics, and user-visible strings. An audit judges each sentence separately; it does not replace a term across the repository with one preferred synonym. The edited sentence preserves actor, action, conditions, order, modality, exceptions, ownership, failure behavior, and consequences. +Comments retain only facts that nearby code cannot express. Documentation stays at its owning level and omits private control flow and rare implementation cases unless they change supported behavior, safe use, compatibility, data integrity, security, or another maintained contract. The [simplification workflow](../../../skills/dsh-find-simplifications/SKILL.md) applies this rule while surveying code and prose together. + Exact code identifiers, public APIs, durable fields, protocol members, type names, headings with external references, and filenames stay unchanged unless a coordinated contract rename is independently required. Surrounding prose explains their fields or behavior directly. Generated documents and catalogs update from their owning source. Before using `contract`, `boundary`, or `shape`, writers check whether the sentence means a more specific rule, operation, data structure, field set, validation point, timing point, API, type, or failure condition. `Contract` remains correct for preconditions, postconditions, invariants, compatibility promises, and other obligations that callers, callees, implementers, providers, producers, or consumers rely on. `Boundary` remains correct for a literal security, trust, wire, process, serialization, transaction, or lifecycle division. `Shape` remains correct when the structural form itself is the subject and no narrower term such as fields, schema, type, union variant, file layout, or export form states the fact. Code and API names containing these words remain unchanged unless a separate coordinated rename is required. diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md index 8a45d452bf..0202be5c22 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md @@ -16,6 +16,8 @@ Status: implemented 该规则适用于 Markdown、README、活跃 Agent Note、JSDoc 与注释、提示词、诊断信息和用户可见字符串。审查会分别判断每个句子,不会在整个仓库中用一个偏好的近义词统一替换某个术语。编辑后的句子保留执行者、动作、条件、顺序、情态、例外、归属、失败行为和后果。 +注释只保留附近代码无法表达的事实。文档停留在内容归属的层级;除非私有控制流或罕见实现情形会改变受支持行为、安全使用、兼容性、数据完整性、安全保障或另一项持续维护的约定,否则文档不描述它们。[简化工作流](../../../skills/dsh-find-simplifications/SKILL.md)在同时巡查代码与行文时应用此规则。 + 除非另一项独立需求明确要求协调重命名约定,否则确切的代码标识符、公开 API、持久字段、协议成员、类型名、带有外部引用的标题和文件名均保持不变。它们周围的行文直接说明其字段或行为。生成的文档和目录在维护它们的源文件修改后更新。 使用 `contract`、`boundary` 或 `shape` 之前,写作者要确认句子是否实际指更具体的规则、操作、数据结构、字段集合、校验点、时间点、API、类型或失败条件。调用方、被调用方、实现方、提供方、生产方或消费方依赖的前置条件、后置条件、不变量、兼容性承诺及其他义务仍可准确称为 `contract`。真实的安全、信任、wire、进程、序列化、事务或生命周期分界仍可准确称为 `boundary`。当结构形式本身就是主题,且字段、schema、类型、联合变体、文件布局或导出形式等更窄的词无法说明事实时,仍可使用 `shape`。除非另一项独立需求要求协调重命名,否则包含这些词的代码和 API 名称保持不变。 diff --git a/.agents/skills/dsh-find-simplifications/SKILL.md b/.agents/skills/dsh-find-simplifications/SKILL.md index 2a10999c27..ef7f387c4e 100644 --- a/.agents/skills/dsh-find-simplifications/SKILL.md +++ b/.agents/skills/dsh-find-simplifications/SKILL.md @@ -1,6 +1,6 @@ --- name: dsh-find-simplifications -description: 'Use when working in the deepseek-harness repo to find non-obvious simplification candidates, write proposed Agent Notes or inline TODO/FIXME/XXX notes, audit or coalesce superseded Agent Notes, or fold worthwhile simplification ideas from another PR; especially for dead, duplicated, speculative, over-built, added-then-removed, or hand-rolled-where-a-dependency-exists surfaces.' +description: 'Use when working in the deepseek-harness repo to find non-obvious simplification candidates, remove redundant comments or implementation-heavy documentation, write proposed Agent Notes or inline TODO/FIXME/XXX notes, audit or coalesce superseded Agent Notes, or fold worthwhile simplification ideas from another PR; especially for dead, duplicated, speculative, over-built, added-then-removed, or hand-rolled-where-a-dependency-exists surfaces.' --- # Finding DeepSeek Harness Simplifications @@ -44,6 +44,13 @@ If subagents are unavailable, simulate the same breadth yourself. Do not let the Start with the largest production-code deltas. A broad simplification audit that stops after obvious unused symbols can miss the files where duplicated lifecycle or defensive machinery carries most of the cost. +## Simplify Prose With The Code + +Treat comments and documentation as maintained surface area. Apply [dsh-prose-standard](../dsh-prose-standard/SKILL.md) when a survey includes prose. + +- Delete comments that restate code or explain behavior owned elsewhere; keep required local contracts. +- Keep docs at their owning level; omit implementation details and rare cases unless they change a maintained contract. + ## Audit Trust And Lifecycle Boundaries For every defensive copy, freeze, validator, and callback capture, name where the value came from and who owns it next. Same-process typed service/plugin calls ordinarily borrow readonly values; parsers, config loaders, queues, model/tool JSON, durable files, workers, processes, and wire decoders own or validate their data. Tests built around hostile getters, fake typed objects, callback replacement, or mutation after a same-process handoff are evidence of a potentially speculative contract, not automatic justification for keeping it. diff --git a/AGENTS.md b/AGENTS.md index a66d6cdc54..0a026bdb83 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -117,7 +117,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, - **Source plane vs artifact plane, never mixed.** Static gates and tests resolve workspace imports through tsconfig `paths` to `src` and pass on a clean tree; gates consuming built `lib/` declare that dependency ([layout](docs/development.md#typescript-project-layout)). - **Keep compiler faces explicit.** Each package uses one aggregate except `api/remotes`; repo-wide programs seed a face config, never the root solution ([layout](docs/development.md#typescript-project-layout)). - **An empty `catch` names what it swallows** and why nothing else can reach it; keep the `try` to one statement. -- Do not comment on facts obvious from code. +- **Keep comments local.** Do not restate code, explain distant behavior unless locally required, or expand unrelated comments ([rationale](.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)). - **Prefer symmetry for parallel values**; unexplained asymmetry usually signals a missed extraction. - **Tests describe behavior, not correctness.** Change obsolete behavior with its tests; explain why in the PR. - **Non-trivial changes MUST include an Agent Note in the same PR;** only mechanical/local edits are exempt ([scope](.agents/notes/README.md#when-to-write-one)). Archived notes are frozen: never edit or treat them as current authority ([archive policy](.agents/notes/README.md#archiving-and-deletion)). diff --git a/apps/cli/src/bin.ts b/apps/cli/src/bin.ts index 9aa44f8b22..321849f2d9 100644 --- a/apps/cli/src/bin.ts +++ b/apps/cli/src/bin.ts @@ -1,8 +1,6 @@ #!/usr/bin/env node /** - * dsh — command-line entry. Dynamic imports per mode keep unrelated modes out - * of each dispatch path; the adapter prints and exits for - * `--help`/`--version`/a parse error, so only a valid mode reaches the switch. + * Command-line entry for dsh. * @module @deepseek-ai/dsh/bin */ @@ -16,7 +14,6 @@ import { parseDshArgs } from './args.ts' // Both the source tree (apps/cli/src) and the bundled bin (apps/cli/lib) sit // one directory under apps/cli, so the checked-in manifest resolves with the // same relative hop from either artifact. -/** This app's version, read from its checked-in package.json. */ function readVersion(): string { const manifest = JSON.parse( readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf8'), diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index 7359ce5ade..2ff361f3db 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -1,8 +1,4 @@ -/** - * Web application entry: thin bootstrap over the shell library. Everything — - * module-table seeding, the boot page, and the UI-renderer handoff — lives - * in @deepseek-ai/dsh-client-web; this file only finds the mount point. - */ +/** Browser entry for the Web client. */ import { AppWebEntry } from '@deepseek-ai/dsh-client-web' const el = document.getElementById('root') diff --git a/apps/web/src/node-module-stub.ts b/apps/web/src/node-module-stub.ts index 0a9b04ea5f..15e2c1c8da 100644 --- a/apps/web/src/node-module-stub.ts +++ b/apps/web/src/node-module-stub.ts @@ -3,10 +3,10 @@ * configured loader path and fails loud if that assumption changes. */ -/** Throwing stand-in for node:module's createRequire (never reached in the browser boot). */ +/** Fail if browser boot reaches Node's module loader. */ export const createRequire = (): never => { throw new Error('node:module is not available in the browser') } -/** Erased type peer for the vendored loader's type-only LoadHookContext import. */ +/** Type-only peer for the vendored loader. */ export type LoadHookContext = never diff --git a/apps/web/tests/approval-composer.e2e.ts b/apps/web/tests/approval-composer.e2e.ts index e57367ee46..9739c13b07 100644 --- a/apps/web/tests/approval-composer.e2e.ts +++ b/apps/web/tests/approval-composer.e2e.ts @@ -1,16 +1,5 @@ -// Web e2e scenario: the composer-takeover approval panel under a long -// command. The shipped composition confines bash through the sandbox policy -// and routes its escalation through the approval seam, so a read-only session -// asked to write a file produces a REAL pending approval — the panel renders -// in the browser, the test measures its geometry, answers through it, and the -// escalated command then runs. Replay is deterministic: the denial, the -// escalation retry and its command text arrive from replayed chunks, and the -// answer click is the test's own gesture (the same sanctioned reaction to -// model content as the question composer: the turn cannot complete without it). -// -// Geometry is the point of the scenario. The command is unbounded model text, -// and an uncapped card grows with it until the refuse/allow buttons leave the -// viewport — an approval the user could see and not answer. +// Browser geometry for a pending approval whose model-supplied command would +// push the actions outside the viewport without a capped text region. import { readFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { join } from 'node:path' @@ -29,17 +18,12 @@ import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './suppor const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/approval-composer', import.meta.url)) const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl') -// The scenario's one golden: the waiting panel. Everything the answered state -// proves is asserted directly — see the world-state block at the end. +// The golden covers the stable waiting panel; direct assertions cover its answer. const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md') const MODE = webSnapshotMode() -// Irreducible payload: the command has to be long enough to pass the card's -// height cap, which is the only command length that reproduces an action row pushed off -// screen. Unrelated tokens, not a repeated word — a repeated word is what the -// model compressed into `printf 'alpha %.0s' {1..400}` while recording, and a -// short command proves nothing here. The formula keeps the source small; the -// model receives the expanded literal it has to put in the command. +// Unrelated tokens keep the recorded model from compressing the payload into a +// short shell loop that would not overflow the card. const TOKENS = Array.from({ length: 220 }, (_, index) => `tok${((index + 1) * 7919 % 99991).toString(36)}`).join(' ') const PROMPT = `Write a file named notes.txt in the workspace containing exactly this text on one line: ${TOKENS}. Use one bash command with the literal text inline. Then reply with the single word DONE and stop.` @@ -77,19 +61,12 @@ describe('web e2e: approval takeover keeps its actions reachable', () => { const input = page.locator('textarea').first() await input.waitFor({ timeout: 10_000 }) - // The composer's own text cap, measured on the live draft scrollport before - // the takeover replaces it — the box that carries the cap, while the - // textarea inside it is as tall as the whole draft. The panel's scroll - // region must stop at the same height (the designer's requirement: one cap - // for the composer seat), and measuring it here keeps the assertion free of - // the px value itself. + // Derive the expected cap from the live composer instead of duplicating its pixel value. await input.fill(CAP_PROBE) const composerCap = await input.evaluate(el => el.closest('[data-input-scroll]')?.clientHeight ?? 0) expect(composerCap).toBeGreaterThan(0) await input.fill('') - // Read-only: the mode whose denial the model escalates from. Switched - // through the shipped access-mode chip, not a test-only override. await page.locator('[aria-label^="Access mode"]').click() await page.getByRole('menuitem', { name: 'Read Only' }).click() await expect.poll( @@ -101,22 +78,15 @@ describe('web e2e: approval takeover keeps its actions reachable', () => { await input.fill(PROMPT) await input.press('Enter') - // The panel takes over the input area while the tool blocks. Its presence - // is a STABLE waiting state (it stays until answered), so waitFor is - // race-free. const panel = page.locator('[data-approval-key]') await panel.waitFor({ timeout: MODE === 'record' ? 180_000 : 60_000 }) const scroll = panel.locator('[data-approval-scroll]') await expect.poll(() => scroll.getByText(/tok/).count(), { timeout: 15_000 }).toBeGreaterThan(0) if (MODE !== 'record') { - // This golden owns the stable waiting surface; the answered golden below - // owns the resulting transcript. const snapshot = await captureStableAria(page, '[data-approval-key]', scaffold.workspaceCwd) await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE) - // The uncapped-card hazard the header names, measured at the lane - // baseline and at a short viewport, on the live panel. const original = page.viewportSize() ?? { width: 1680, height: 1000 } for (const height of [1000, 700]) { await page.setViewportSize({ width: 900, height }) @@ -140,11 +110,8 @@ describe('web e2e: approval takeover keeps its actions reachable', () => { }) expect(geometry.buttons).toBe(2) expect(geometry.scrolls).toBe(true) - // One cap for the seat: the panel's text region stops where the - // composer draft does (sub-pixel tolerance for the shared padding). + // The panel and composer share one cap; allow sub-pixel layout variance. expect(Math.abs(geometry.capped - composerCap)).toBeLessThan(1) - // Both buttons stay inside the card AND inside the viewport — the - // answerable state the cap exists to guarantee. expect(geometry.actionsTop).toBeGreaterThan(0) expect(geometry.actionsBottom).toBeLessThanOrEqual(geometry.viewport) expect(geometry.actionsBottom).toBeLessThanOrEqual(geometry.cardBottom) @@ -159,12 +126,8 @@ describe('web e2e: approval takeover keeps its actions reachable', () => { await recordFixture(scaffold, sessionId, FIXTURE) return } - // World state: the granted escalation is what let the command run, and the - // panel leaves with the regular composer restored. Asserted on the world - // and the DOM rather than through a transcript golden — the denied first - // attempt renders the OS's own refusal ("Operation not permitted" on - // macOS, "Read-only file system" on Linux), so the answered transcript is - // not a platform-neutral golden surface. + // The denied attempt contains platform-specific OS text, so direct state + // and DOM assertions cover the answered outcome. expect(JSON.stringify(sessionEvents.filter(e => e.type === 'approval/decided').at(-1))) .toContain('allowed-once') const written = await readFile(join(scaffold.workspaceCwd, 'workspace', 'notes.txt'), 'utf8') diff --git a/apps/web/tests/background-job-list.e2e.ts b/apps/web/tests/background-job-list.e2e.ts index 0e0a98de93..b0fdc62ddb 100644 --- a/apps/web/tests/background-job-list.e2e.ts +++ b/apps/web/tests/background-job-list.e2e.ts @@ -1,8 +1,5 @@ -// Web e2e scenario: the session-header background-job list over the real -// host. No model call is involved — a genuine `run_in_background` bash call -// registers with `ctx.jobs`, and the assertion chain is the whole delivery -// path: registry change feed → api-proxy `session/jobs` frame → the client's -// `jobsBySession` mirror → the header action. +// Session-header background jobs driven by a real `ctx.jobs` entry. No model +// call is involved. import { readFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { join } from 'node:path' @@ -30,7 +27,7 @@ const SEED_ID = 'background-job-list-web-e2e' const COMMAND = 'sleep 45' /** - * Wait for the Host to publish the live Agent that opening a session resumes. + * Wait for opening a session to publish its live Agent. * @param scaffold - the booted web scaffold. * @param sessionId - the opened session's identity. * @returns the registered Agent instance. @@ -82,9 +79,7 @@ describe.skipIf(MODE === 'record')('web e2e: background job list', () => { it('shows a running background job in the session header without a refresh', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-background-job-running')) - // Point assertion, not a poll: `expect.poll` retries until a predicate - // holds, so polling for zero passes at t=0 and proves nothing. The - // "renders nothing without a task" branch is owned by the component suite. + // Polling for zero would pass at t=0 before delivery and prove nothing. const trigger = page.getByRole('button', { name: '1 background job running' }) expect(await trigger.count()).toBe(0) @@ -116,8 +111,6 @@ describe.skipIf(MODE === 'record')('web e2e: background job list', () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-background-job-settled')) expect(scaffold.ctx.jobs.kill(jobId, agent, 'web e2e cancellation')).toBe('requested') - // The trigger drops its live count once the task leaves running/stopping, - // which is also the proof that settlement reached the browser unprompted. const idle = page.getByRole('button', { name: '1 background job' }) await idle.waitFor({ timeout: 20_000 }) diff --git a/apps/web/tests/code-mode-round.e2e.ts b/apps/web/tests/code-mode-round.e2e.ts index 051700dc35..342f9328e0 100644 --- a/apps/web/tests/code-mode-round.e2e.ts +++ b/apps/web/tests/code-mode-round.e2e.ts @@ -1,11 +1,4 @@ -// Web e2e scenario: a Code Mode round trip. The scaffold boots the SAME -// shipped tree with the tools row patched to mode: code (the run_code-only -// wire), a real chromium sends a prompt engineered to elicit one run_code -// program with several sub-calls, and the UI must render the code-variant -// parent row with its always-visible nested sub-rows — each sub-row the same -// component a native call renders through — plus details-panel resolution for -// a clicked sub-row. Drive steps wait only on generic completion -// (whenTurnSettled); assertion steps run in replay/refresh only. +// Code Mode browser round trip with nested sub-calls and details selection. // Record: DSH_SNAPSHOT=record rewrites session.jsonl, then a keyless // DSH_SNAPSHOT=refresh regenerates ui.expected.md. import { readFile } from 'node:fs/promises' @@ -24,9 +17,7 @@ const FIXTURE = fileURLToPath(new URL('./snapshots/code-mode-round/session.jsonl const UI_EXPECTED = fileURLToPath(new URL('./snapshots/code-mode-round/ui.expected.md', import.meta.url)) const MODE = webSnapshotMode() -// The scenario's one drive prompt: elicits one program with a bash sub-call -// and a failing read the program tolerates — the sub-row set the assertions -// need. Never asserted against model prose. +// Elicits the successful and failed sub-rows this scenario asserts. const PROMPT = 'Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt ' + 'catching its error in the program. Return an object with both outcomes. Then reply DONE and stop.' @@ -48,7 +39,6 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { tripwire = watchConsole(page) await page.goto(scaffold.baseUrl, { waitUntil: 'load' }) await page.waitForSelector('[class*="frame"]', { timeout: 30_000 }) - // Fresh world: connect a Workspace so the composer scenarios start live. await connectFreshWorkspace(page, scaffold.workspaceCwd) }, 120_000) @@ -60,7 +50,6 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { it('drives the recorded prompt to a settled turn (all modes)', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-drive')) if (MODE !== 'record') { - // Drift guard: the committed fixture must carry exactly the drive prompt. expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT]) } const input = page.locator('textarea').first() @@ -75,11 +64,9 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { }, 200_000) it.skipIf(MODE === 'record')('the durable log carries run_code with full-content sub-dispatches', () => { - // Wire discipline: code mode collapsed the call surface to run_code. const calls = sessionEvents.filter(event => event.type === 'tool/call') expect(calls.length).toBeGreaterThanOrEqual(1) expect(new Set(calls.map(call => (call.data as { name: string }).name))).toEqual(new Set(['run_code'])) - // Sub-dispatches logged with the complete tool/result vocabulary. const dispatches = sessionEvents.filter(event => (event.type as string) === 'tool/code-dispatch') expect(dispatches.length).toBeGreaterThanOrEqual(2) for (const dispatch of dispatches) { @@ -107,14 +94,9 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { // description as its summary (the presentCall contract). const codeRow = page.locator('[data-variant="code"]').first() await codeRow.waitFor({ timeout: 10_000 }) - // Nested rows are visible WITHOUT any expand interaction, inside the - // sub-call nest, each rendered by the same components as native rows: - // the bash sub-call landed in the bash sample registration. const nest = page.locator('[data-subcalls]').first() await nest.waitFor({ timeout: 10_000 }) expect(await nest.locator('[data-sample="bash"]').count()).toBeGreaterThanOrEqual(1) - // The failing read sub-call wears the same error state a native failed - // row wears (the recorded program tolerates a read of missing.txt). expect(await nest.locator('[data-state="error"]').count()).toBeGreaterThanOrEqual(1) }, 60_000) @@ -124,7 +106,6 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { const frame = page.locator('[style*="grid-template-columns"]').first() expect(await frame.getAttribute('data-details-collapsed')).toBe('true') await nest.locator('[data-sample="bash"]').first().click() - // Tool rows do not drive layout geometry; the Session's default panel stays closed. await expect.poll(() => frame.getAttribute('data-details-collapsed'), { timeout: 5_000 }).toBe('true') }) diff --git a/apps/web/tests/composer-draft-scroll.e2e.ts b/apps/web/tests/composer-draft-scroll.e2e.ts index ae48dcddca..b8fa9a6121 100644 --- a/apps/web/tests/composer-draft-scroll.e2e.ts +++ b/apps/web/tests/composer-draft-scroll.e2e.ts @@ -1,35 +1,6 @@ -// Web e2e scenario: a composer draft longer than the 14-line cap scrolls its -// GLYPHS AND ITS CARET AS ONE. -// -// The composer paints its text in two stacked layers (see -// packages/client/ui-conversation/src/client/skeleton/InputBar.module.css): the -// `