@codexview/react 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/components/CodexTranscript.d.ts +41 -0
- package/dist/components/ErrorBlock.d.ts +7 -0
- package/dist/components/ExecBlock.d.ts +9 -0
- package/dist/components/ItemErrorBoundary.d.ts +16 -0
- package/dist/components/Markdown.d.ts +20 -0
- package/dist/components/MessageBubble.d.ts +10 -0
- package/dist/components/PatchBlock.d.ts +9 -0
- package/dist/components/RawEventBlock.d.ts +7 -0
- package/dist/components/ReasoningBlock.d.ts +11 -0
- package/dist/components/SearchBlock.d.ts +10 -0
- package/dist/components/StatusBar.d.ts +10 -0
- package/dist/components/TodoListBlock.d.ts +9 -0
- package/dist/components/ToolCallBlock.d.ts +9 -0
- package/dist/components/TurnContainer.d.ts +7 -0
- package/dist/components/_shared.d.ts +2 -0
- package/dist/components/icons.d.ts +16 -0
- package/dist/hooks/useCodexTranscript.d.ts +10 -0
- package/dist/hooks/useSmoothStream.d.ts +7 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +13520 -0
- package/dist/index.js.map +1 -0
- package/dist/reducer/status.d.ts +2 -0
- package/dist/reducer/transcript.d.ts +4 -0
- package/dist/styles.css +493 -0
- package/dist/types/events.d.ts +164 -0
- package/dist/types/model.d.ts +75 -0
- package/docs/api.md +156 -0
- package/docs/changelog.md +40 -0
- package/docs/events.md +65 -0
- package/docs/integration-agentweb.md +112 -0
- package/docs/styling.md +81 -0
- package/docs/superpowers/plans/2026-05-15-codexview-implementation.md +3903 -0
- package/docs/superpowers/specs/2026-05-15-codexview-design.md +661 -0
- package/package.json +84 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PatchFile, SearchResult, TodoEntry, TokenUsage } from './events.js';
|
|
2
|
+
/** Item-level lifecycle status (5 states per spec §5.1). */
|
|
3
|
+
export type ItemStatus = 'pending' | 'running' | 'completed' | 'failed' | 'stopped';
|
|
4
|
+
/** Discriminated kind of a rendered item. */
|
|
5
|
+
export type ItemKind = 'user_message' | 'reasoning' | 'assistant_text' | 'tool_call' | 'exec' | 'search' | 'patch' | 'todo_list' | 'error' | 'raw';
|
|
6
|
+
interface ItemViewBase {
|
|
7
|
+
id: string;
|
|
8
|
+
kind: ItemKind;
|
|
9
|
+
status: ItemStatus;
|
|
10
|
+
startedAt: number;
|
|
11
|
+
updatedAt: number;
|
|
12
|
+
}
|
|
13
|
+
export type ItemView = (ItemViewBase & {
|
|
14
|
+
kind: 'user_message';
|
|
15
|
+
text: string;
|
|
16
|
+
}) | (ItemViewBase & {
|
|
17
|
+
kind: 'reasoning';
|
|
18
|
+
text: string;
|
|
19
|
+
}) | (ItemViewBase & {
|
|
20
|
+
kind: 'assistant_text';
|
|
21
|
+
text: string;
|
|
22
|
+
}) | (ItemViewBase & {
|
|
23
|
+
kind: 'tool_call';
|
|
24
|
+
name: string;
|
|
25
|
+
server?: string;
|
|
26
|
+
args: unknown;
|
|
27
|
+
result?: unknown;
|
|
28
|
+
error?: string;
|
|
29
|
+
}) | (ItemViewBase & {
|
|
30
|
+
kind: 'exec';
|
|
31
|
+
command: string;
|
|
32
|
+
exit?: number;
|
|
33
|
+
stdout?: string;
|
|
34
|
+
stderr?: string;
|
|
35
|
+
durationMs?: number;
|
|
36
|
+
}) | (ItemViewBase & {
|
|
37
|
+
kind: 'search';
|
|
38
|
+
query: string;
|
|
39
|
+
results?: SearchResult[];
|
|
40
|
+
}) | (ItemViewBase & {
|
|
41
|
+
kind: 'patch';
|
|
42
|
+
files: PatchFile[];
|
|
43
|
+
ok?: boolean;
|
|
44
|
+
}) | (ItemViewBase & {
|
|
45
|
+
kind: 'todo_list';
|
|
46
|
+
items: TodoEntry[];
|
|
47
|
+
}) | (ItemViewBase & {
|
|
48
|
+
kind: 'error';
|
|
49
|
+
message: string;
|
|
50
|
+
}) | (ItemViewBase & {
|
|
51
|
+
kind: 'raw';
|
|
52
|
+
payload: unknown;
|
|
53
|
+
});
|
|
54
|
+
export interface TurnView {
|
|
55
|
+
turnId: string;
|
|
56
|
+
startedAt: number;
|
|
57
|
+
completedAt?: number;
|
|
58
|
+
status: 'running' | 'completed' | 'failed' | 'aborted';
|
|
59
|
+
items: ItemView[];
|
|
60
|
+
usage?: TokenUsage;
|
|
61
|
+
error?: {
|
|
62
|
+
message: string;
|
|
63
|
+
code?: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export interface TranscriptModel {
|
|
67
|
+
threadId?: string;
|
|
68
|
+
turns: TurnView[];
|
|
69
|
+
lastEventAt: number;
|
|
70
|
+
}
|
|
71
|
+
/** Empty initial model. Always safe to start reducing from here. */
|
|
72
|
+
export declare const EMPTY_MODEL: TranscriptModel;
|
|
73
|
+
/** Session-level status (5 states per spec §5.3). */
|
|
74
|
+
export type TranscriptStatus = 'idle' | 'working' | 'completed' | 'stopped' | 'failed';
|
|
75
|
+
export {};
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
All exports from `codexview`. Import via `import { ... } from 'codexview'`.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### `<CodexTranscript>`
|
|
8
|
+
|
|
9
|
+
Top-level transcript renderer.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<CodexTranscript
|
|
13
|
+
events={events}
|
|
14
|
+
status?={status}
|
|
15
|
+
error?={{ message, details? }}
|
|
16
|
+
className?={string}
|
|
17
|
+
maxItems?={number}
|
|
18
|
+
emptyState?={ReactNode}
|
|
19
|
+
onItemClick?={(id) => void}
|
|
20
|
+
components?={Partial<CodexTranscriptComponents>}
|
|
21
|
+
disableSmoothStream?={boolean}
|
|
22
|
+
onInternalError?={(err, event?) => void}
|
|
23
|
+
/>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Prop | Default | Notes |
|
|
27
|
+
|------|---------|-------|
|
|
28
|
+
| `events` | required | Append-only is most efficient; full replace also works. Reference equality skips re-reduce. |
|
|
29
|
+
| `status` | inferred | When provided, fully replaces `inferStatus(model)`. |
|
|
30
|
+
| `error` | undefined | Shown inside `StatusBar` when status is `failed` or `stopped`. |
|
|
31
|
+
| `className` | undefined | Appended to `.codexview-root` class list. |
|
|
32
|
+
| `maxItems` | unlimited | Trims oldest items, shows "已省略最早的 X 条" hint at the top. |
|
|
33
|
+
| `emptyState` | `'暂无对话'` | Rendered when `events` is empty. |
|
|
34
|
+
| `onItemClick` | undefined | Receives `item.id` of the clicked item. |
|
|
35
|
+
| `components` | builtin | Replace any subcomponent (e.g. `{ ToolCallBlock: MyTool }`). |
|
|
36
|
+
| `disableSmoothStream` | `false` | Disables the typewriter effect for `assistant_text` and `reasoning`. |
|
|
37
|
+
| `onInternalError` | undefined | Called when reducer or item render throws; component falls back without crashing. |
|
|
38
|
+
|
|
39
|
+
#### Minimal example
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
const events = useAtomValue(streamingAtomFamily(sessionId));
|
|
43
|
+
return <CodexTranscript events={events.list} />;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `<StatusBar>`
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<StatusBar status={status} label?={string} error?={{ message, details? }} />
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Returns `null` when `status === 'idle'`.
|
|
53
|
+
|
|
54
|
+
### `<TurnContainer>`
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<TurnContainer turn={turn}>{children}</TurnContainer>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `<MessageBubble>`
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<MessageBubble item={userOrAssistantItem} smoothStream?={boolean} />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `<ReasoningBlock>`
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<ReasoningBlock item={reasoningItem} defaultOpen?={boolean} smoothStream?={boolean} />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `<ToolCallBlock>`
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<ToolCallBlock item={toolCallItem} />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Renders args inline; result inline if small, collapsed in `<details>` if long (`>500` chars, `>4` lines, or `>3` JSON depth).
|
|
79
|
+
|
|
80
|
+
### `<ExecBlock>`
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<ExecBlock item={execItem} />
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Shimmer bar shown while `status === 'running'`. stdout/stderr collapsed beyond size threshold.
|
|
87
|
+
|
|
88
|
+
### `<SearchBlock>`
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<SearchBlock item={searchItem} initialVisible?={number} />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Shows first 3 results by default; "展开剩余 N 条" reveals the rest.
|
|
95
|
+
|
|
96
|
+
### `<PatchBlock>`
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<PatchBlock item={patchItem} />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Each file is collapsed; expanding shows diff with git-style coloring.
|
|
103
|
+
|
|
104
|
+
### `<RawEventBlock>`
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<RawEventBlock item={rawItem} />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Used as fallback for unknown event types.
|
|
111
|
+
|
|
112
|
+
### `<ItemErrorBoundary>`
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
<ItemErrorBoundary fallback?={ReactNode} onError?={(err, info) => void}>
|
|
116
|
+
{children}
|
|
117
|
+
</ItemErrorBoundary>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Hooks
|
|
121
|
+
|
|
122
|
+
### `useCodexTranscript(events, options?)`
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const { model, status } = useCodexTranscript(events, { status?, onInternalError? });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Internally caches the last reduced model and uses prefix detection for incremental reduction.
|
|
129
|
+
|
|
130
|
+
### `useSmoothStream(text, options?)`
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const display = useSmoothStream(fullText, { enabled?, charsPerFrame?, minDelayMs? });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Returns the substring revealed so far. Resets when input shrinks. Returns full text immediately if `enabled === false` or `prefers-reduced-motion: reduce`.
|
|
137
|
+
|
|
138
|
+
## Pure functions
|
|
139
|
+
|
|
140
|
+
### `reduceTranscript(prev, event) => next`
|
|
141
|
+
|
|
142
|
+
Pure reducer. Use directly to drive your own state container.
|
|
143
|
+
|
|
144
|
+
### `inferStatus(model) => TranscriptStatus`
|
|
145
|
+
|
|
146
|
+
Returns `'idle' | 'working' | 'completed' | 'stopped' | 'failed'`.
|
|
147
|
+
|
|
148
|
+
### `EMPTY_MODEL`
|
|
149
|
+
|
|
150
|
+
Frozen initial `TranscriptModel`. Always safe to start reducing from this.
|
|
151
|
+
|
|
152
|
+
## Types
|
|
153
|
+
|
|
154
|
+
`ChatStreamEvent`, `ChatStreamEventType`, `TokenUsage`, `SearchResult`, `PatchFile`, `TranscriptModel`, `TurnView`, `ItemView`, `ItemKind`, `ItemStatus`, `TranscriptStatus`, `CodexTranscriptComponents`, `UseCodexTranscriptOptions`, `UseSmoothStreamOptions`, plus per-component prop types (`StatusBarProps`, etc.).
|
|
155
|
+
|
|
156
|
+
See [docs/events.md](events.md) for the full `ChatStreamEvent` shape.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
4
|
+
|
|
5
|
+
## [0.1.1] — 2026-05-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- New item kinds aligned with the official [OpenAI Codex SDK](https://github.com/openai/codex/tree/main/sdk/typescript):
|
|
10
|
+
- `todo_list` — plan / TODO list with completion tracking; new `<TodoListBlock>` component
|
|
11
|
+
- `error` — non-fatal item-level error (distinct from `turn_failed`); new `<ErrorBlock>` component
|
|
12
|
+
- `<Markdown>` component (powered by `react-markdown` + `remark-gfm`): tables, task lists, autolinks, inline code, fenced code blocks, blockquotes
|
|
13
|
+
- Markdown rendering integrated into `MessageBubble`, `ReasoningBlock`, and `ToolCallBlock` (string-typed results auto-detected as Markdown)
|
|
14
|
+
- New event types: `todo_list`, `error_item`
|
|
15
|
+
- New exported types: `TodoEntry`, `MarkdownProps`, `TodoListBlockProps`, `ErrorBlockProps`
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- All tool-related blocks (`ToolCallBlock`, `ExecBlock`, `SearchBlock`, `PatchBlock`) are now collapsed by default. They auto-open while their status is `pending` or `running`. New `defaultOpen` prop available on each.
|
|
20
|
+
- `MessageBubble` and `ReasoningBlock` accept a `markdown` prop (default `true`) to render text as Markdown. Streaming partial text falls back to plain text until completion to avoid janky partial-Markdown rendering.
|
|
21
|
+
|
|
22
|
+
### Dependencies
|
|
23
|
+
|
|
24
|
+
- Added: `react-markdown` `^10.1.0`, `remark-gfm` `^4.0.1`
|
|
25
|
+
|
|
26
|
+
## [0.1.0] — 2026-05-15
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `<CodexTranscript>` main component (consumes `ChatStreamEvent[]`)
|
|
31
|
+
- Subcomponents: `StatusBar`, `TurnContainer`, `MessageBubble`, `ReasoningBlock`, `ToolCallBlock`, `ExecBlock`, `SearchBlock`, `PatchBlock`, `RawEventBlock`, `ItemErrorBoundary`
|
|
32
|
+
- Hooks: `useCodexTranscript`, `useSmoothStream`
|
|
33
|
+
- Pure functions: `reduceTranscript`, `inferStatus`, `EMPTY_MODEL`
|
|
34
|
+
- 8 supported event item kinds + raw fallback for unknown types
|
|
35
|
+
- 5-state item status machine, 4-state turn status machine, 5-state session status
|
|
36
|
+
- CSS Modules styling with overridable CSS variables
|
|
37
|
+
- `lucide-react` icon system as peerDependency
|
|
38
|
+
- `prefers-reduced-motion` honored by all animations
|
|
39
|
+
- 6 fixtures + replay integration tests
|
|
40
|
+
- Docs: API reference, event contract, styling, agentweb integration guide
|
package/docs/events.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Event contract
|
|
2
|
+
|
|
3
|
+
CodexView consumes a discriminated union `ChatStreamEvent`. The host (e.g. agentweb backend) is expected to emit events that match these shapes. Unknown event types are not errors — they fall through to `kind: 'raw'` and render via `RawEventBlock`.
|
|
4
|
+
|
|
5
|
+
All events carry `at: number` (epoch ms).
|
|
6
|
+
|
|
7
|
+
## Lifecycle
|
|
8
|
+
|
|
9
|
+
| Type | Required fields | Reducer effect |
|
|
10
|
+
|------|-----------------|----------------|
|
|
11
|
+
| `thread_started` | `threadId` | sets `model.threadId` |
|
|
12
|
+
| `turn_started` | `turnId` | appends new `TurnView { status: 'running' }` |
|
|
13
|
+
| `turn_completed` | `turnId`, optional `usage` | turn → `completed`; flips unfinished items → `completed` |
|
|
14
|
+
| `turn_failed` | `turnId`, `error: { message, code? }` | turn → `failed`; flips unfinished items → `failed` |
|
|
15
|
+
| `turn_aborted` | `turnId`, optional `reason` | turn → `aborted`; flips unfinished items → `stopped` |
|
|
16
|
+
|
|
17
|
+
## Messages
|
|
18
|
+
|
|
19
|
+
| Type | Required fields | ItemView produced |
|
|
20
|
+
|------|-----------------|-------------------|
|
|
21
|
+
| `user_message` | `turnId`, `itemId`, `text` | `kind: 'user_message'`, status `completed` |
|
|
22
|
+
| `agent_message` | `turnId`, `itemId`, `text`, `partial` | `kind: 'assistant_text'`; same `itemId` updates in place; `partial: false` flips to `completed` |
|
|
23
|
+
| `reasoning` | same as `agent_message` | `kind: 'reasoning'`; **never merged** with assistant_text |
|
|
24
|
+
|
|
25
|
+
## Tool calls (paired by `callId`)
|
|
26
|
+
|
|
27
|
+
| Type | Required fields | Reducer effect |
|
|
28
|
+
|------|-----------------|----------------|
|
|
29
|
+
| `function_call` | `turnId`, `callId`, `name`, `args` | appends `tool_call`, status `pending` |
|
|
30
|
+
| `function_call_output` | `turnId`, `callId`, optional `output` or `error` | finds matching `tool_call`, sets `result` or `error`, flips status |
|
|
31
|
+
| `mcp_tool_call` | `turnId`, `callId`, `server`, `name`, `args` | same as `function_call` plus `server` |
|
|
32
|
+
| `mcp_tool_call_output` | `turnId`, `callId`, optional `output` or `error` | same as `function_call_output` |
|
|
33
|
+
|
|
34
|
+
## Shell exec
|
|
35
|
+
|
|
36
|
+
| Type | Required fields | Reducer effect |
|
|
37
|
+
|------|-----------------|----------------|
|
|
38
|
+
| `exec_command_begin` | `turnId`, `callId`, `command` | appends `exec`, status `running` |
|
|
39
|
+
| `exec_command_end` | `turnId`, `callId`, `exit`, `stdout`, `stderr`, `durationMs` | finds matching `exec`, fills outputs, status `completed` if `exit === 0` else `failed` |
|
|
40
|
+
|
|
41
|
+
## Web search
|
|
42
|
+
|
|
43
|
+
| Type | Required fields | Reducer effect |
|
|
44
|
+
|------|-----------------|----------------|
|
|
45
|
+
| `web_search_call` | `turnId`, `callId`, `query` | appends `search`, status `pending` |
|
|
46
|
+
| `web_search_end` | `turnId`, `callId`, `results: SearchResult[]` | finds matching `search`, fills results, status `completed` |
|
|
47
|
+
|
|
48
|
+
## Patch apply
|
|
49
|
+
|
|
50
|
+
| Type | Required fields | Reducer effect |
|
|
51
|
+
|------|-----------------|----------------|
|
|
52
|
+
| `patch_apply_end` | `turnId`, `callId`, `files: PatchFile[]`, `ok: boolean` | appends `patch`, status `completed` if `ok` else `failed` |
|
|
53
|
+
|
|
54
|
+
## Fallback
|
|
55
|
+
|
|
56
|
+
| Type | Required fields | Reducer effect |
|
|
57
|
+
|------|-----------------|----------------|
|
|
58
|
+
| `raw` | optional `turnId`, `payload: unknown` | if `turnId` present, appends `raw` ItemView; else only updates `lastEventAt` |
|
|
59
|
+
|
|
60
|
+
## Invariants
|
|
61
|
+
|
|
62
|
+
1. Reducer never throws on any input.
|
|
63
|
+
2. Bulk `events.reduce(reduceTranscript, EMPTY_MODEL)` equals incremental reduce of the same series.
|
|
64
|
+
3. Reducer is a pure function — no global state, no I/O.
|
|
65
|
+
4. Unknown payload `type` is preserved verbatim in the produced `raw` ItemView.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Integrating CodexView into agentweb
|
|
2
|
+
|
|
3
|
+
This is a drop-in replacement for `frontend/src/codex/components/MessageBubble.tsx`, `StreamingBubble.tsx`, and `ToolUseBlock.tsx`. The agentweb backend (`backend/src/codex/eventMap.ts`) produces a normalized event stream that is conceptually equivalent to `ChatStreamEvent`, but uses different field names.
|
|
4
|
+
|
|
5
|
+
## Adapter required
|
|
6
|
+
|
|
7
|
+
The agentweb backend's `NormalizedEvent` and CodexView's `ChatStreamEvent` are conceptually equivalent but use different field names (e.g., agentweb uses `kind: 'text_delta'`, CodexView uses `type: 'agent_message'`). A small adapter function in agentweb (`frontend/src/codex/adapters/codexview.ts`) is needed to map between them. The Step 3 code example below is illustrative — replace `adapter(stream)` with the adapter-produced array and `stream.connected` with whatever connection-state field your `streamingAtomFamily` actually exposes.
|
|
8
|
+
|
|
9
|
+
## Step 1 — install (development)
|
|
10
|
+
|
|
11
|
+
From the agentweb repo root:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm --filter frontend add file:../CodexView
|
|
15
|
+
pnpm --filter frontend add lucide-react
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
(Once `codexview` is published to a registry, replace `file:../CodexView` with the version range.)
|
|
19
|
+
|
|
20
|
+
## Step 2 — load styles + bridge tokens
|
|
21
|
+
|
|
22
|
+
In `frontend/src/main.tsx` (or another global entry):
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import 'codexview/styles.css';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
In `frontend/src/codex/styles/tokens.css` (append):
|
|
29
|
+
|
|
30
|
+
```css
|
|
31
|
+
.aw-codex-transcript {
|
|
32
|
+
--cv-bg-user-bubble: var(--aw-bg-bubble-user);
|
|
33
|
+
--cv-bg-assistant-bubble: var(--aw-bg-bubble-bot);
|
|
34
|
+
--cv-text: var(--aw-text-primary);
|
|
35
|
+
--cv-axis-color: var(--aw-border-subtle);
|
|
36
|
+
--cv-bg-raised: var(--aw-bg-raised);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
(Match the variable list in `docs/styling.md` against agentweb's tokens.)
|
|
41
|
+
|
|
42
|
+
## Step 3 — replace ChatThread internals
|
|
43
|
+
|
|
44
|
+
`frontend/src/codex/components/ChatThread.tsx`:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { useAtomValue } from 'jotai';
|
|
48
|
+
import { CodexTranscript } from 'codexview';
|
|
49
|
+
import { streamingAtomFamily } from '../atoms/streaming';
|
|
50
|
+
import { adaptStreamEvents } from '../adapters/codexview'; // adapter required — see "Adapter required" section above
|
|
51
|
+
|
|
52
|
+
export function ChatThread({ sessionId }: { sessionId: string }) {
|
|
53
|
+
const stream = useAtomValue(streamingAtomFamily(sessionId));
|
|
54
|
+
return (
|
|
55
|
+
<CodexTranscript
|
|
56
|
+
events={adaptStreamEvents(stream)} // replace with adapter-produced ChatStreamEvent[]
|
|
57
|
+
status={stream.connected ? undefined : 'stopped'} // replace stream.connected with actual field name
|
|
58
|
+
className="aw-codex-transcript"
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The adapter maps agentweb's `NormalizedEvent` (with `kind` field) to CodexView's `ChatStreamEvent` (with `type` field). Adjust all property names to match the actual `streamingAtomFamily` shape.
|
|
65
|
+
|
|
66
|
+
## Step 4 — clean up + handle approval
|
|
67
|
+
|
|
68
|
+
Delete from `frontend/src/codex/components/`:
|
|
69
|
+
|
|
70
|
+
- `MessageBubble.tsx`
|
|
71
|
+
- `StreamingBubble.tsx`
|
|
72
|
+
- `ToolUseBlock.tsx`
|
|
73
|
+
|
|
74
|
+
**Keep** approval logic. CodexView v0.1 deliberately does **not** ship an approval-bubble component (see spec §11). If `StreamingBubble.tsx` carried that responsibility, extract the approval portion into a standalone `ApprovalBubble.tsx` component within agentweb and render it next to `<CodexTranscript>` (e.g. as a sibling overlay), feeding it the same approval events.
|
|
75
|
+
|
|
76
|
+
## Verify
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pnpm --filter frontend test
|
|
80
|
+
pnpm --filter frontend dev
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
In the browser:
|
|
84
|
+
|
|
85
|
+
1. Start a Codex session.
|
|
86
|
+
2. Confirm streaming text appears with smooth typewriter effect.
|
|
87
|
+
3. Trigger a tool call — confirm collapsible result.
|
|
88
|
+
4. Trigger an exec — confirm shimmer while running.
|
|
89
|
+
5. Disconnect the network — confirm StatusBar shows "已停止" once you set `status="stopped"`.
|
|
90
|
+
|
|
91
|
+
## Rollback
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git revert <integration-commit-sha>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Backend `ChatStreamEvent` shape and SSE endpoints do not change, so revert is purely frontend.
|
|
98
|
+
|
|
99
|
+
## Type-safety guard
|
|
100
|
+
|
|
101
|
+
Add to `frontend/src/codex/types/eventCheck.ts`:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import type { ChatStreamEvent as CV } from 'codexview';
|
|
105
|
+
import type { ChatStreamEvent as AW } from '../../../../backend/src/codex/eventMap';
|
|
106
|
+
|
|
107
|
+
// Will fail to compile if shapes drift.
|
|
108
|
+
type _AssertEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : never;
|
|
109
|
+
type _Check = _AssertEqual<CV, AW>;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
When the agentweb backend adds an event type, this assertion fails until codexview is updated.
|
package/docs/styling.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Styling
|
|
2
|
+
|
|
3
|
+
CodexView ships its own styles (`codexview/styles.css`) plus a `.codexview-root` reset to isolate from host CSS. All visual tokens are CSS variables you can override.
|
|
4
|
+
|
|
5
|
+
## Loading
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import 'codexview/styles.css';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Variable reference
|
|
12
|
+
|
|
13
|
+
Set these on `.codexview-root` (or any ancestor) to theme.
|
|
14
|
+
|
|
15
|
+
### Layout
|
|
16
|
+
|
|
17
|
+
| Variable | Default | Notes |
|
|
18
|
+
|----------|---------|-------|
|
|
19
|
+
| `--cv-font-family` | system-ui stack | |
|
|
20
|
+
| `--cv-font-mono` | ui-monospace stack | |
|
|
21
|
+
| `--cv-font-size` | `14px` | base text size |
|
|
22
|
+
| `--cv-line-height` | `1.55` | |
|
|
23
|
+
| `--cv-radius` | `12px` | bubble radius |
|
|
24
|
+
| `--cv-radius-sm` | `8px` | block radius |
|
|
25
|
+
| `--cv-spacing-xs/-sm/-md/-lg` | 4/8/12/16 px | spacing scale |
|
|
26
|
+
|
|
27
|
+
### Colors
|
|
28
|
+
|
|
29
|
+
| Variable | Default (light) | Notes |
|
|
30
|
+
|----------|-----------------|-------|
|
|
31
|
+
| `--cv-text` | `#1f2328` | primary text |
|
|
32
|
+
| `--cv-text-muted` | `#6e7781` | reasoning, captions |
|
|
33
|
+
| `--cv-text-inverse` | `#ffffff` | text on user bubble |
|
|
34
|
+
| `--cv-bg` | `#ffffff` | transcript background |
|
|
35
|
+
| `--cv-bg-raised` | `#f6f8fa` | StatusBar, tool block surface |
|
|
36
|
+
| `--cv-bg-user-bubble` | `#2f6feb` | user bubble |
|
|
37
|
+
| `--cv-bg-assistant-bubble` | `#f6f8fa` | assistant bubble |
|
|
38
|
+
| `--cv-bg-code` | `#0d1117` | exec / code background |
|
|
39
|
+
| `--cv-fg-code` | `#e6edf3` | code foreground |
|
|
40
|
+
| `--cv-border` | `#d0d7de` | dividers |
|
|
41
|
+
| `--cv-axis-color` | `#d0d7de` | turn timeline axis |
|
|
42
|
+
| `--cv-shimmer-color` | `rgba(31,35,40,0.08)` | exec shimmer |
|
|
43
|
+
|
|
44
|
+
### Status colors
|
|
45
|
+
|
|
46
|
+
| Variable | Default | Status |
|
|
47
|
+
|----------|---------|--------|
|
|
48
|
+
| `--cv-status-pending` | `#6e7781` | gray |
|
|
49
|
+
| `--cv-status-running` | `#2f6feb` | blue |
|
|
50
|
+
| `--cv-status-completed` | `#1a7f37` | green |
|
|
51
|
+
| `--cv-status-failed` | `#cf222e` | red |
|
|
52
|
+
| `--cv-status-stopped` | `#6e7781` | gray |
|
|
53
|
+
|
|
54
|
+
### Diff colors
|
|
55
|
+
|
|
56
|
+
| Variable | Default |
|
|
57
|
+
|----------|---------|
|
|
58
|
+
| `--cv-diff-add-bg` | `#ddf4e4` |
|
|
59
|
+
| `--cv-diff-del-bg` | `#ffebe9` |
|
|
60
|
+
|
|
61
|
+
## Dark theme example
|
|
62
|
+
|
|
63
|
+
```css
|
|
64
|
+
.dark .codexview-root {
|
|
65
|
+
--cv-text: #e6edf3;
|
|
66
|
+
--cv-text-muted: #8b949e;
|
|
67
|
+
--cv-bg: #0d1117;
|
|
68
|
+
--cv-bg-raised: #161b22;
|
|
69
|
+
--cv-bg-assistant-bubble: #161b22;
|
|
70
|
+
--cv-bg-user-bubble: #1f6feb;
|
|
71
|
+
--cv-border: #30363d;
|
|
72
|
+
--cv-axis-color: #30363d;
|
|
73
|
+
--cv-shimmer-color: rgba(255,255,255,0.05);
|
|
74
|
+
--cv-bg-code: #010409;
|
|
75
|
+
--cv-fg-code: #c9d1d9;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Reduced motion
|
|
80
|
+
|
|
81
|
+
All animations (pulse, shimmer, blink caret, smooth stream) honor `prefers-reduced-motion: reduce` and degrade to static states automatically.
|