deer-flow/frontend/src/AGENTS.md
Xinmin Zeng 2a97ec3e58
docs: turn README into an index and move reference detail to docs/guide (#8113)
* docs: turn README into an index and move reference detail to docs/guide

The README had grown to 393 KB because every change appended its notes there. The text moves verbatim into 18 topic guides under docs/guide/; only headings are re-leveled and relative links re-pointed. The README keeps the introduction, a quick start and a guide index. Contributor test notes move to CONTRIBUTING.md.

* docs: send user docs to topic guides and budget the root README

The rule 'update README.md for user-facing changes' is what grew the README. Guidance now points to docs/guide/, and check_agent_guidance.py fails when the root README exceeds 16 KiB (AG003).

* chore: remove unreferenced screenshots and finished plan notes

pr-build/ holds two PR screenshots (3 MB) from #1986. plans/ holds the plan for the merged #4049. docs/SKILL_NAME_CONFLICT_FIX.md is a February change note. Nothing references them.

* docs: route always-loaded AGENTS.md files to scoped guides

* docs(agents): cut scoped guides to scope, verify, traps, decisions

Each AGENTS.md keeps four kinds of content: the scope of the directory, the
verification commands, traps a local test run does not show, and decisions the
code cannot state. Values, field lists, function inventories, test names, issue
numbers and history are deleted, because the code, the tests and git state them.

Search and fetch providers share one community guide. Four guides with no
remaining content are removed. The size check now allows 6 KiB per file (hard
limit 8 KiB) and 24 KiB per chain (hard limit 32 KiB).

* style: format frontend guide table and guidance test imports

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-11 13:26:17 +08:00

5.0 KiB

Frontend source (frontend/src/)

app/ holds routes, components/workspace/ the chat UI, and core/ the framework-free logic (threads, messages, API client, settings). Ownership of the model picker, chat entry pages, branch tree and browser control is described in conversation-ui-ownership.md.

Verify

cd frontend
pnpm check                       # eslint + tsc
pnpm rstest run <pattern>        # unit and happy-dom tests under tests/unit
pnpm test:e2e                    # Playwright, for anything that depends on a real browser

Threads and streaming

  • Two thread identities. A new chat has a display ID before the SDK has a threadId. Key local-turn state on the displayed conversation so that it survives the moment the SDK ID appears.
  • Creating a run is not retried. Only the first POST /runs/stream has retries off, because a retry after an ambiguous failure creates a second run. Stream reads keep the SDK retry budget.
  • A stream gap is not the end of a run. When the server reports a replay gap, reload durable state and rejoin. Do not finish the turn and do not cancel the backend run.
  • Do not rejoin a completed run. Recovery for a reopened chat lists active runs, and that read can be stale. Run IDs that the mounted hook has seen complete stay excluded.
  • Unsupported stream modes fail before the request. Forwarding part of the list, or defaulting to values, hides a caller bug.
  • Stream updates use the SDK throttle: true mode. A numeric delay changes the trailing-debounce behaviour on a continuous stream; validate that before you switch.
  • No timers during render. Streamed tool updates re-render often. Schedule timers in an effect with cleanup.
  • Queue and Steer. Release a rejected attempt only after an HTTP 409 with no recovered receipt. After an uncertain failure keep its run, request ID and draft, because the server may have accepted the input.

Messages

  • Match by message ID. History, checkpoint and live copies of a message are overlaid by message ID. Provider tool-call IDs repeat across runs.
  • One message, two renderers. A tool-calling AI message can also carry visible text, and a streaming text message can gain tool calls later. It is drawn by the processing group first and by the settled bubble afterwards, so text and reasoning order must be the same in both.
  • Run-scoped displays have one anchor. Duration and workspace-change badges belong to a run, and a run can end in several assistant bubbles. Choose the anchor in core/messages and render it once.
  • Tool failures come from structured status. Use core/messages/tool-error.ts. Classifying result text marks normal output as an error.
  • Exports read every persisted page. The loaded window and the compacted checkpoint both omit messages.

State and storage

  • Server state is authoritative. ThreadState.artifacts is the artifact list. Session storage keeps panel UI state only, and an empty first stream value must not overwrite restored state before history loads.
  • A truncated preview is never an editing baseline. Its ETag can equal the full file's. Editing and saving need the complete content.
  • Account preferences are scoped to the account. A late response for a previous account is dropped, and legacy unscoped settings are never uploaded. Automatic model or mode resolution writes nothing to the account. Only an explicit user choice does.
  • Composer drafts are per tab. New-chat pages use a stable scope because their runtime thread ID changes on every reload.
  • Credentials stay out of browser storage. The session and CSRF cookies belong to the Gateway.

Untrusted input

  • Links in tool steps and Markdown come from the model or a provider. Pass them through the shared isSafeHref allowlist and render rejected ones with UnsafeLink.
  • Knowledge sources come only from native tool artifacts. Message text and metadata cannot create a source. Excerpts render as plain text.
  • Plugin mention IDs, imported Agent packages and uploaded names are input for the Gateway to validate. They grant nothing in the UI.
  • Maps keyed by external strings use own properties, so constructor and __proto__ fall back like any unknown key.

Rendering

  • Server Components by default. app/layout.tsx reads no cookies and imports no chat-only styles, so the public / stays static.
  • Build chat paths with core/threads/utils.ts::pathOfThread(). It encodes agent names and thread IDs.
  • Streaming Markdown plugins come from the shared core/streamdown config. Keep wrapper behaviour in workspace components and leave the generated AI elements unedited.
  • The three right-hand panels share one ResizablePanelGroup in chat-box.tsx. A separate branch per panel loses the drag handle.
  • Composer permission checks run in InputBox.handleSubmit after the command is classified and before the thread is prepared or the draft cleared.