6 6
7> Une simulation interactive de la façon dont la fenêtre de contexte de Claude Code se remplit pendant une session. Voyez ce qui se charge automatiquement, ce que coûte chaque lecture de fichier, et quand les règles et les hooks s'exécutent.7> Une simulation interactive de la façon dont la fenêtre de contexte de Claude Code se remplit pendant une session. Voyez ce qui se charge automatiquement, ce que coûte chaque lecture de fichier, et quand les règles et les hooks s'exécutent.
8 8
9export const ContextWindow = () => {
10 const MAX = 200000;
11 const STARTUP_END = 0.2;
12 {}
13 const EVENTS = useMemo(() => [{}, {
14 t: 0.015,
15 kind: 'auto',
16 label: 'System prompt',
17 tokens: 4200,
18 color: '#6B6964',
19 vis: 'hidden',
20 desc: 'Core instructions for behavior, tool use, and response formatting. Always loaded first. You never see it.',
21 link: null
22 }, {
23 t: 0.035,
24 kind: 'auto',
25 label: 'Auto memory (MEMORY.md)',
26 tokens: 680,
27 color: '#E8A45C',
28 vis: 'hidden',
29 desc: "Claude's notes to itself from previous sessions: build commands it learned, patterns it noticed, mistakes to avoid. The first 200 lines or 25KB, whichever comes first, are loaded into the conversation context.",
30 link: '/en/memory#auto-memory'
31 }, {
32 t: 0.06,
33 kind: 'auto',
34 label: 'Environment info',
35 tokens: 280,
36 color: '#6B6964',
37 vis: 'hidden',
38 desc: 'Working directory, platform, shell, OS version, and whether this is a git repo. Git branch, status, and recent commits load as a separate block.',
39 link: null
40 }, {
41 t: 0.08,
42 kind: 'auto',
43 label: 'MCP tools (deferred)',
44 tokens: 120,
45 color: '#9B7BC4',
46 vis: 'hidden',
47 desc: 'MCP tool names listed so Claude knows what is available. By default, full schemas stay deferred and Claude loads specific ones on demand via tool search when a task needs them. Set `ENABLE_TOOL_SEARCH=auto` to load schemas upfront when they fit within 10% of the context window, or `ENABLE_TOOL_SEARCH=false` to load everything.',
48 link: '/en/mcp#scale-with-mcp-tool-search'
49 }, {
50 t: 0.1,
51 kind: 'auto',
52 label: 'Skill descriptions',
53 tokens: 450,
54 color: '#D4A843',
55 vis: 'hidden',
56 noSurviveCompact: true,
57 desc: 'One-line descriptions of available skills so Claude knows what it can invoke. Full skill content loads only when Claude actually uses one. Skills with `disable-model-invocation: true` are not in this list. They stay completely out of context until you invoke them with `/name`. Unlike the rest of the startup content, this listing is not re-injected after `/compact`. Only skills you actually invoked get preserved.',
58 link: '/en/skills'
59 }, {
60 t: 0.12,
61 kind: 'auto',
62 label: '~/.claude/CLAUDE.md',
63 tokens: 320,
64 color: '#6A9BCC',
65 vis: 'hidden',
66 desc: 'Your global preferences. Applies to every project. Loaded alongside project instructions at the start of every conversation.',
67 link: '/en/memory#choose-where-to-put-claude-md-files'
68 }, {
69 t: 0.14,
70 kind: 'auto',
71 label: 'Project CLAUDE.md',
72 tokens: 1800,
73 color: '#6A9BCC',
74 vis: 'hidden',
75 desc: 'Project conventions, build commands, architecture notes. The most important file you can create. Lives in your project root, so your whole team gets the same instructions.',
76 tip: 'Keep it under 200 lines. Move reference content to skills or path-scoped rules so it only loads when needed.',
77 link: '/en/memory'
78 }, {}, {
79 t: 0.22,
80 kind: 'user',
81 label: 'Your prompt',
82 tokens: 45,
83 color: '#558A42',
84 vis: 'full',
85 desc: '"Fix the auth bug where users get 401 after token refresh"',
86 link: null
87 }, {}, {
88 t: 0.28,
89 kind: 'claude',
90 label: 'Read src/api/auth.ts',
91 tokens: 2400,
92 color: '#8A8880',
93 vis: 'brief',
94 restoredAfterCompact: true,
95 desc: 'Main auth file. You see "Read auth.ts" in your terminal, but the 2,400 tokens of file content only Claude sees.',
96 tip: 'File reads dominate context usage. Be specific in prompts ("fix the bug in auth.ts") so Claude reads fewer files. For research-heavy tasks, use a subagent.',
97 link: null
98 }, {
99 t: 0.32,
100 kind: 'claude',
101 label: 'Read src/lib/tokens.ts',
102 tokens: 1100,
103 color: '#8A8880',
104 vis: 'brief',
105 restoredAfterCompact: true,
106 desc: 'Following imports to the token module. Shown as a one-liner in your terminal.',
107 link: null
108 }, {
109 t: 0.35,
110 kind: 'auto',
111 label: 'Rule: api-conventions.md',
112 tokens: 380,
113 color: '#4A9B8E',
114 vis: 'brief',
115 desc: 'This rule in `.claude/rules/` has a `paths:` pattern matching `src/api/**`. It loaded automatically when Claude read a file in that directory. You see "Loaded .claude/rules/api-conventions.md" in your terminal, but not the rule content.',
116 link: '/en/memory#path-specific-rules'
117 }, {
118 t: 0.38,
119 kind: 'claude',
120 label: 'Read middleware.ts',
121 tokens: 1800,
122 color: '#8A8880',
123 vis: 'brief',
124 restoredAfterCompact: true,
125 desc: 'Tracing the auth flow deeper.',
126 link: null
127 }, {
128 t: 0.41,
129 kind: 'claude',
130 label: 'Read auth.test.ts',
131 tokens: 1600,
132 color: '#8A8880',
133 vis: 'brief',
134 restoredAfterCompact: true,
135 desc: 'Checking existing tests for expected behavior.',
136 link: null
137 }, {
138 t: 0.44,
139 kind: 'auto',
140 label: 'Rule: testing.md',
141 tokens: 290,
142 color: '#4A9B8E',
143 vis: 'brief',
144 desc: 'Another path-scoped rule, this one matching `*.test.ts` files. Triggered when Claude read auth.test.ts. Shown as a one-line "Loaded" notice.',
145 link: '/en/memory#path-specific-rules'
146 }, {
147 t: 0.47,
148 kind: 'claude',
149 label: 'grep "refreshToken"',
150 tokens: 600,
151 color: '#A09E96',
152 vis: 'brief',
153 desc: 'Search results across the codebase. You see the command ran, not the full output.',
154 link: null
155 }, {}, {
156 t: 0.53,
157 kind: 'claude',
158 label: "Claude's analysis",
159 tokens: 800,
160 color: '#D97757',
161 vis: 'full',
162 desc: 'Explains the bug: token invalidated too early in the rotation. This text appears in your terminal.',
163 link: null
164 }, {
165 t: 0.57,
166 kind: 'claude',
167 label: 'Edit auth.ts',
168 tokens: 400,
169 color: '#D97757',
170 vis: 'full',
171 desc: 'Fixes the token rotation order. The diff appears in your terminal.',
172 link: null
173 }, {
174 t: 0.59,
175 kind: 'hook',
176 label: 'Hook: prettier',
177 tokens: 120,
178 color: '#B8860B',
179 vis: 'hidden',
180 desc: 'A PostToolUse hook in `settings.json` runs prettier after every file edit and reports back via `hookSpecificOutput.additionalContext`. That field enters Claude\'s context. Plain stdout on exit 0 does not. It is written to the debug log only.',
181 tip: 'Output JSON with `additionalContext` to send info to Claude. For PostToolUse hooks, exit code 2 surfaces stderr as an error but cannot block since the tool already ran. Output over 10,000 characters is saved to a file; Claude gets a preview and the file path instead.',
182 link: '/en/hooks-guide'
183 }, {
184 t: 0.62,
185 kind: 'claude',
186 label: 'Edit auth.test.ts',
187 tokens: 600,
188 color: '#D97757',
189 vis: 'full',
190 desc: 'Adds a regression test for the fix. The diff appears in your terminal.',
191 link: null
192 }, {
193 t: 0.64,
194 kind: 'hook',
195 label: 'Hook: prettier',
196 tokens: 100,
197 color: '#B8860B',
198 vis: 'hidden',
199 desc: 'The same hook fires again for the test file. Every matching tool event triggers it.',
200 link: '/en/hooks-guide'
201 }, {
202 t: 0.67,
203 kind: 'claude',
204 label: 'npm test output',
205 tokens: 1200,
206 color: '#A09E96',
207 vis: 'brief',
208 desc: 'Runs the test suite. You see "Running npm test..." and the pass count, not the full 1,200 tokens of output.',
209 link: null
210 }, {
211 t: 0.70,
212 kind: 'claude',
213 label: 'Summary',
214 tokens: 400,
215 color: '#D97757',
216 vis: 'full',
217 desc: '"Fixed token rotation. Added regression test. All tests pass."',
218 link: null
219 }, {}, {
220 t: 0.72,
221 kind: 'user',
222 label: 'Your follow-up',
223 tokens: 40,
224 color: '#558A42',
225 vis: 'full',
226 desc: '"Use a subagent to research session timeout handling, then fix it"',
227 tip: 'Follow-ups add to the same context. Delegating research to a subagent keeps large file reads out of your main window.',
228 link: null
229 }, {
230 t: 0.79,
231 kind: 'claude',
232 label: 'Spawn research subagent',
233 tokens: 80,
234 color: '#D97757',
235 vis: 'brief',
236 desc: "Claude delegates the research to a subagent with a fresh, separate context window. It loads CLAUDE.md and the same MCP and skill setup, but starts without your conversation history or the main session's auto memory.",
237 link: '/en/sub-agents'
238 }, {
239 t: 0.795,
240 kind: 'sub',
241 label: 'System prompt',
242 tokens: 0,
243 subTokens: 900,
244 color: '#6B6964',
245 vis: 'hidden',
246 desc: "The subagent gets its own system prompt, shorter than the main session's. For the general-purpose agent, it's a brief prompt plus environment details. The main session's auto memory is not included. If a custom agent has memory: in its frontmatter, it loads its own separate MEMORY.md here instead.",
247 link: '/en/sub-agents#enable-persistent-memory'
248 }, {
249 t: 0.80,
250 kind: 'sub',
251 label: 'Project CLAUDE.md (own copy)',
252 tokens: 0,
253 subTokens: 1800,
254 color: '#6A9BCC',
255 vis: 'hidden',
256 desc: "The subagent loads CLAUDE.md too. Same file, same content, but it counts against the subagent's context, not yours. The built-in Explore and Plan agents skip this for a smaller context.",
257 link: '/en/sub-agents'
258 }, {
259 t: 0.805,
260 kind: 'sub',
261 label: 'MCP tools + skills',
262 tokens: 0,
263 subTokens: 970,
264 color: '#9B7BC4',
265 vis: 'hidden',
266 desc: "The subagent has access to the same MCP servers and skills. It gets most of the parent's tools, minus several that don't apply in a nested context, including plan-mode controls, background-task tools, and by default the Agent tool itself to prevent recursion.",
267 link: '/en/sub-agents'
268 }, {
269 t: 0.81,
270 kind: 'sub',
271 label: 'Task prompt from main',
272 tokens: 0,
273 subTokens: 120,
274 color: '#558A42',
275 vis: 'hidden',
276 desc: "Instead of a user prompt, the subagent receives the task Claude wrote for it: 'Research session timeout handling in this codebase.'",
277 link: '/en/sub-agents'
278 }, {
279 t: 0.82,
280 kind: 'sub',
281 label: 'Read session.ts',
282 tokens: 0,
283 subTokens: 2200,
284 color: '#8A8880',
285 vis: 'hidden',
286 desc: "Now the subagent does its work. This file read fills the subagent's context, not yours.",
287 link: '/en/sub-agents'
288 }, {
289 t: 0.825,
290 kind: 'sub',
291 label: 'Read timeouts.ts',
292 tokens: 0,
293 subTokens: 800,
294 color: '#8A8880',
295 vis: 'hidden',
296 desc: "Another file read in the subagent's separate context.",
297 link: '/en/sub-agents'
298 }, {
299 t: 0.83,
300 kind: 'sub',
301 label: 'Read config/*.ts',
302 tokens: 0,
303 subTokens: 3100,
304 color: '#8A8880',
305 vis: 'hidden',
306 desc: "The subagent can read as many files as it needs. None of this touches your main context.",
307 link: '/en/sub-agents'
308 }, {
309 t: 0.85,
310 kind: 'claude',
311 label: 'Subagent returns summary',
312 tokens: 420,
313 color: '#D97757',
314 vis: 'brief',
315 desc: "Only the subagent's final text response comes back to your context, plus a small metadata trailer with token counts and duration. The subagent read 6,100 tokens of files. You got a 420-token result. That's the context savings.",
316 link: '/en/sub-agents'
317 }, {
318 t: 0.86,
319 kind: 'claude',
320 label: "Claude's response",
321 tokens: 1200,
322 color: '#D97757',
323 vis: 'full',
324 desc: 'Analysis and fix for session timeouts. This text appears in your terminal.',
325 link: null
326 }, {}, {
327 t: 0.875,
328 kind: 'user',
329 label: '!git status',
330 tokens: 180,
331 color: '#558A42',
332 vis: 'full',
333 desc: "You ran a shell command with the ! prefix to see which files Claude modified. The command and its output both enter context as part of your message. Useful for grounding Claude in command output without Claude running it.",
334 link: '/en/interactive-mode#shell-mode-with-prefix'
335 }, {
336 t: 0.89,
337 kind: 'user',
338 label: '/commit-push',
339 tokens: 620,
340 color: '#558A42',
341 vis: 'brief',
342 restoredAfterCompact: true,
343 desc: 'You invoked a skill that has `disable-model-invocation: true`. Its description was not in the skill index at startup, so it cost zero context until this moment. Now the full skill content loads and Claude follows its instructions to stage, commit, and push your changes. After `/compact`, Claude Code re-injects the body of each skill you invoked, capped at 5,000 tokens per skill.',
344 tip: 'Set `disable-model-invocation: true` on skills with side effects like committing, deploying, or sending messages. They stay out of context entirely until you need them.',
345 link: '/en/skills#control-who-invokes-a-skill'
346 }, {}, {
347 t: 0.93,
348 kind: 'compact',
349 label: '/compact',
350 tokens: 0,
351 color: '#D97757',
352 vis: 'brief',
353 desc: 'Replaces the conversation with a structured summary. You see a "Conversation compacted" message. The summarization happens without appearing in your terminal.',
354 link: '/en/how-claude-code-works#the-context-window'
355 }].filter(e => e.t !== undefined), []);
356 const VIS_META = {
357 hidden: {
358 label: 'Invisible in your terminal',
359 sub: 'This content does not appear in your terminal.'
360 },
361 brief: {
362 label: 'One-liner in your terminal',
363 sub: 'You see a brief mention, not the full content.'
364 },
365 full: {
366 label: 'Shown in your terminal',
367 sub: 'The actual content appears in your terminal.'
368 }
369 };
370 {}
371 const GATES = [{
372 at: 0.18,
373 kind: 'prompt',
374 text: 'Fix the auth bug where users get 401 after token refresh',
375 resumeTo: 0.22
376 }, {
377 at: 0.705,
378 kind: 'prompt',
379 text: 'Use a subagent to research session timeout handling, then fix it',
380 resumeTo: 0.72
381 }, {
382 at: 0.865,
383 kind: 'bang',
384 text: '!git status',
385 resumeTo: 0.875
386 }, {
387 at: 0.88,
388 kind: 'slash',
389 text: '/commit-push',
390 resumeTo: 0.89
391 }, {
392 at: 0.90,
393 kind: 'compact',
394 text: '/compact',
395 resumeTo: 1
396 }];
397 const KIND_META = {
398 auto: {
399 badge: 'auto',
400 detail: 'Auto-loaded',
401 badgeBg: 'rgba(94,93,89,0.15)',
402 badgeColor: '#8A8880'
403 },
404 user: {
405 badge: 'you',
406 detail: 'You typed this',
407 badgeBg: 'rgba(85,138,66,0.15)',
408 badgeColor: '#6BA656'
409 },
410 claude: {
411 badge: 'claude',
412 detail: "Claude's work",
413 badgeBg: 'rgba(217,119,87,0.12)',
414 badgeColor: '#D97757'
415 },
416 hook: {
417 badge: 'hook',
418 detail: 'Hook (automatic)',
419 badgeBg: 'rgba(184,134,11,0.15)',
420 badgeColor: '#CCA020'
421 },
422 compact: {
423 badge: 'compact',
424 detail: 'Compaction',
425 badgeBg: 'rgba(217,119,87,0.12)',
426 badgeColor: '#D97757'
427 },
428 sub: {
429 badge: 'subagent',
430 detail: "In subagent's context",
431 badgeBg: 'rgba(155,123,196,0.12)',
432 badgeColor: '#9B7BC4'
433 }
434 };
435 const LEGEND = [{
436 c: '#6B6964',
437 l: 'System'
438 }, {
439 c: '#6A9BCC',
440 l: 'CLAUDE.md'
441 }, {
442 c: '#E8A45C',
443 l: 'Memory'
444 }, {
445 c: '#D4A843',
446 l: 'Skills'
447 }, {
448 c: '#9B7BC4',
449 l: 'MCP'
450 }, {
451 c: '#4A9B8E',
452 l: 'Rules'
453 }, {
454 c: '#558A42',
455 l: 'You'
456 }, {
457 c: '#8A8880',
458 l: 'Files'
459 }, {
460 c: '#A09E96',
461 l: 'Output'
462 }, {
463 c: '#D97757',
464 l: 'Claude'
465 }, {
466 c: '#B8860B',
467 l: 'Hooks'
468 }];
469 const fmt = n => n >= 1000 ? (n / 1000).toFixed(1).replace(/\.0$/, '') + 'K' : n + '';
470 const [time, setTime] = useState(0);
471 const [playing, setPlaying] = useState(false);
472 const [hovIdx, setHovIdx] = useState(null);
473 const [selIdx, setSelIdx] = useState(null);
474 const [hovCat, setHovCat] = useState(null);
475 const [gatesPassed, setGatesPassed] = useState(0);
476 const [mounted, setMounted] = useState(false);
477 const [hasInteracted, setHasInteracted] = useState(false);
478 const lastRef = useRef(null);
479 const scrollRef = useRef(null);
480 const detailRef = useRef(null);
481 useEffect(() => setMounted(true), []);
482 const activeGate = GATES.find((g, i) => i >= gatesPassed && time >= g.at && time < g.resumeTo);
483 useEffect(() => {
484 if (!playing) return;
485 let raf;
486 let stopped = false;
487 const tick = ts => {
488 if (stopped) return;
489 if (!lastRef.current) lastRef.current = ts;
490 const dt = (ts - lastRef.current) / 1000;
491 lastRef.current = ts;
492 setTime(prev => {
493 const next = prev + dt * 0.032;
494 const gate = GATES.find((g, i) => i >= gatesPassed && next >= g.at && prev < g.resumeTo);
495 if (gate) {
496 stopped = true;
497 setPlaying(false);
498 return gate.at;
499 }
500 if (next >= 1) {
501 stopped = true;
502 setPlaying(false);
503 return 1;
504 }
505 return next;
506 });
507 if (!stopped) raf = requestAnimationFrame(tick);
508 };
509 raf = requestAnimationFrame(tick);
510 return () => {
511 stopped = true;
512 cancelAnimationFrame(raf);
513 lastRef.current = null;
514 };
515 }, [playing, gatesPassed]);
516 const sendPrompt = () => {
517 if (!activeGate) return;
518 const isCompact = activeGate.kind === 'compact';
519 setGatesPassed(n => n + 1);
520 setTime(activeGate.resumeTo);
521 setSelIdx(null);
522 setHovIdx(null);
523 if (!isCompact) setPlaying(true);
524 };
525 const visibleCount = EVENTS.filter(e => e.t <= time).length;
526 const preCompactVisible = useMemo(() => EVENTS.slice(0, visibleCount), [EVENTS, visibleCount]);
527 const compactGateIdx = GATES.length - 1;
528 const isCompacted = gatesPassed > compactGateIdx && preCompactVisible.some(e => e.kind === 'compact');
529 const {visible, preCompactTotal} = useMemo(() => {
530 const nonCompact = preCompactVisible.filter(e => e.kind !== 'compact');
531 if (!isCompacted) {
532 return {
533 visible: preCompactVisible,
534 preCompactTotal: 0
535 };
536 }
537 {}
538 const autoLoads = nonCompact.filter(e => e.kind === 'auto' && e.t < STARTUP_END && !e.noSurviveCompact);
539 const restored = nonCompact.filter(e => e.restoredAfterCompact);
540 const summarized = nonCompact.filter(e => e.t >= STARTUP_END && e.kind !== 'sub');
541 const sumTokens = summarized.reduce((s, e) => s + e.tokens, 0);
542 const summaryBlock = {
543 t: STARTUP_END,
544 kind: 'compact',
545 label: 'Conversation summary',
546 tokens: Math.round(sumTokens * 0.12),
547 color: '#A09E96',
548 vis: 'hidden',
549 desc: `All ${summarized.length} conversation events condensed into one structured summary. The summary keeps: your requests and intent, key technical concepts, files examined or modified with important code snippets, errors and how they were fixed, pending tasks, and current work. It replaces the verbatim conversation: full tool outputs and intermediate reasoning are gone. Claude Code re-reads the files modified most recently and re-injects the skills you invoked, listed below. Claude can still reference the rest of the work but won't have the exact content.`,
550 link: '/en/how-claude-code-works#the-context-window'
551 };
552 return {
553 visible: [...autoLoads, summaryBlock, ...restored],
554 preCompactTotal: nonCompact.reduce((s, e) => s + e.tokens, 0)
555 };
556 }, [preCompactVisible, isCompacted]);
557 const {blocks, totalTokens} = useMemo(() => {
558 const bl = visible.map((e, visIdx) => ({
559 ...e,
560 id: e.label + e.t,
561 visIdx
562 })).filter(e => e.tokens > 0 || e.label === 'Conversation summary');
563 return {
564 blocks: bl,
565 totalTokens: bl.reduce((s, b) => s + b.tokens, 0)
566 };
567 }, [visible]);
568 const subTotal = useMemo(() => visible.filter(e => e.kind === 'sub').reduce((s, e) => s + (e.subTokens || 0), 0), [visible]);
569 useEffect(() => {
570 if (!scrollRef.current) return;
571 if (isCompacted) scrollRef.current.scrollTo({
572 top: 0,
573 behavior: 'smooth'
574 }); else if (playing || activeGate) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
575 }, [visible.length, !!activeGate, isCompacted]);
576 const rootRef = useRef(null);
577 const keyStateRef = useRef({});
578 const [isFullscreen, setIsFullscreen] = useState(false);
579 keyStateRef.current = {
580 time,
581 activeGate,
582 sendPrompt,
583 hasInteracted
584 };
585 useEffect(() => {
586 const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
587 document.addEventListener('fullscreenchange', onFsChange);
588 return () => document.removeEventListener('fullscreenchange', onFsChange);
589 }, []);
590 const toggleFullscreen = () => {
591 if (!rootRef.current) return;
592 if (document.fullscreenElement) document.exitFullscreen(); else rootRef.current.requestFullscreen().catch(() => {});
593 };
594 useEffect(() => {
595 const onKey = e => {
596 const tag = e.target.tagName;
597 if (tag === 'INPUT' || tag === 'BUTTON' || tag === 'TEXTAREA' || tag === 'SELECT' || e.target.isContentEditable) return;
598 if (!rootRef.current) return;
599 const rect = rootRef.current.getBoundingClientRect();
600 if (rect.width === 0 && rect.height === 0) return;
601 if (rect.bottom < 0 || rect.top > window.innerHeight) return;
602 if (e.code === 'Space') {
603 const {time: t, activeGate: g, sendPrompt: send, hasInteracted: hi} = keyStateRef.current;
604 if (!hi) return;
605 e.preventDefault();
606 if (t === 0) setPlaying(true); else if (g) send(); else if (t >= 1) {
607 setTime(0);
608 setGatesPassed(0);
609 setSelIdx(null);
610 setHovIdx(null);
611 setPlaying(true);
612 } else setPlaying(p => !p);
613 }
614 };
615 window.addEventListener('keydown', onKey);
616 return () => window.removeEventListener('keydown', onKey);
617 }, []);
618 const pct = totalTokens / MAX * 100;
619 const barColor = pct > 75 ? '#D97757' : pct > 50 ? '#B8860B' : '#558A42';
620 const activeIdx = selIdx !== null ? selIdx : hovIdx;
621 const hovEvent = activeIdx !== null ? visible[activeIdx] : null;
622 useEffect(() => {
623 if (detailRef.current) detailRef.current.scrollTop = 0;
624 }, [hovEvent]);
625 const focusT = hovEvent ? hovEvent.t : time;
626 const takeaway = isCompacted ? 'Compaction replaces the conversation with a structured summary. System prompt, CLAUDE.md, memory, and MCP tools reload automatically. Claude Code also re-reads up to five of the files modified most recently and re-injects the skills you invoked. The skill listing does not reload.' : focusT < STARTUP_END ? 'A lot loads before you type anything. CLAUDE.md, memory, skills, and MCP tools are all in context before your first prompt.' : focusT < 0.28 ? "Your prompt is tiny compared to what's already loaded. Most of Claude's context is project knowledge, not your words." : focusT < 0.50 ? 'Each file Claude reads grows the context. Path-scoped rules load automatically alongside matching files.' : focusT < 0.71 ? 'Hooks fire automatically on tool events. Output reaches Claude via additionalContext JSON. Exit code 2 surfaces stderr to Claude. Plain stdout on exit 0 goes to the debug log, not the transcript.' : focusT < 0.79 ? 'Follow-up questions keep building on the same context. Everything from earlier is still there.' : focusT < 0.87 ? "The subagent works in its own separate context window. None of its file reads touch yours. Only the final summary comes back." : focusT < 0.88 ? 'Bang commands run in your shell and prefix the output to your next message. Useful for grounding Claude in command results without it running them.' : focusT < 0.90 ? 'User-only skills stay out of context entirely until you invoke them. The skill index at startup only lists skills Claude can call on its own.' : '/compact summarizes the conversation to free space while keeping key information. In a real session, run it when context starts affecting performance or before a long new task.';
627 const terminalView = isCompacted ? 'A "Conversation compacted" message, then a one-line "Read auth.ts" for each re-read file and "Skills restored (commit-push)". None of the content itself appears.' : focusT < STARTUP_END ? 'The input box, waiting for your first message. Everything above loads silently before you type anything.' : focusT < 0.28 ? 'Your prompt. Claude hasn\'t started working yet.' : focusT < 0.52 ? 'Your prompt and "Reading files...". Rules show as one-line "Loaded" notices, not their content.' : focusT < 0.72 ? "Claude's response and file diffs. Hooks fire silently. Tool output like npm test shows as a brief summary, not the full content." : focusT < 0.79 ? 'Your follow-up prompt.' : focusT < 0.86 ? "A brief notice that a subagent is working, then its result. You don't see the subagent's individual file reads." : focusT < 0.90 ? "Claude's response, your git status output, and the commit-push skill running." : 'Your full conversation. /compact is available to run.';
628 const mono = 'var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)';
629 const renderWithCode = s => s.split('`').map((part, i) => i % 2 === 1 ? <code key={i} style={{
630 fontFamily: mono,
631 fontSize: '0.92em',
632 background: 'var(--cw-track)',
633 padding: '1px 4px',
634 borderRadius: 3
635 }}>{part}</code> : part);
636 if (!mounted) return null;
637 return <>
638 <div className="cw-mobile-fallback">
639 This interactive timeline works best on a larger screen. See <a href="#what-the-timeline-shows" style={{
640 color: '#D97757'
641 }}>the written breakdown below</a> for the same concepts.
642 </div>
643 <div className="cw-root" ref={rootRef} onClickCapture={() => setHasInteracted(true)} style={isFullscreen ? {
644 height: '100vh',
645 borderRadius: 0,
646 display: 'flex',
647 flexDirection: 'column'
648 } : {}}>
649 <style>{`
650 .cw-root {
651 --cw-bg: #FAFAF8;
652 --cw-text: #1A1918;
653 --cw-text-2: #3D3C38;
654 --cw-text-3: #5E5D59;
655 --cw-text-dim: #6E6C64;
656 --cw-text-faint: #8A8880;
657 --cw-surface: rgba(0,0,0,0.025);
658 --cw-surface-2: rgba(0,0,0,0.04);
659 --cw-border: rgba(0,0,0,0.08);
660 --cw-track: rgba(0,0,0,0.04);
661 --cw-hover: rgba(0,0,0,0.04);
662 --cw-rail: rgba(0,0,0,0.08);
663 --cw-scrollbar: rgba(0,0,0,0.22);
664 background: var(--cw-bg);
665 border-radius: 12px;
666 overflow: hidden;
667 font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, sans-serif);
668 color: var(--cw-text);
669 border: 1px solid var(--cw-border);
670 }
671 .dark .cw-root {
672 --cw-bg: #111110;
673 --cw-text: #E8E6DC;
674 --cw-text-2: #B8B6AE;
675 --cw-text-3: #9C9A92;
676 --cw-text-dim: #8A8880;
677 --cw-text-faint: #6E6C64;
678 --cw-surface: rgba(255,255,255,0.02);
679 --cw-surface-2: rgba(255,255,255,0.015);
680 --cw-border: rgba(255,255,255,0.06);
681 --cw-track: rgba(255,255,255,0.03);
682 --cw-hover: rgba(255,255,255,0.04);
683 --cw-rail: rgba(255,255,255,0.04);
684 --cw-scrollbar: rgba(255,255,255,0.18);
685 }
686 .cw-scroll::-webkit-scrollbar { width: 6px; }
687 .cw-scroll::-webkit-scrollbar-track { background: transparent; }
688 .cw-scroll::-webkit-scrollbar-thumb { background: var(--cw-scrollbar); border-radius: 3px; }
689 @keyframes cw-blink { 50% { opacity: 0; } }
690 @keyframes cw-fadein { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
691 .cw-compacted-row { animation: cw-fadein 0.3s ease-out backwards; }
692 .cw-mobile-fallback { display: none; padding: 14px 16px; border-radius: 8px; font-size: 14px; border: 1px solid rgba(0,0,0,0.1); background: rgba(0,0,0,0.03); }
693 .dark .cw-mobile-fallback { border-color: rgba(255,255,255,0.15); background: rgba(255,255,255,0.04); }
694 @media (max-width: 700px) {
695 .cw-root { display: none !important; }
696 .cw-mobile-fallback { display: block; }
697 }
698 `}</style>
699
700 {}
701 <div style={{
702 padding: '16px 20px 12px',
703 display: 'flex',
704 alignItems: 'flex-end',
705 gap: 24
706 }}>
707 <div style={{
708 flex: 1,
709 minWidth: 0
710 }}>
711 <div style={{
712 fontSize: 18,
713 fontWeight: 600,
714 letterSpacing: -0.3,
715 lineHeight: 1
716 }}>
717 Explore the context window
718 </div>
719 <div style={{
720 fontSize: 14,
721 color: 'var(--cw-text-dim)',
722 marginTop: 4
723 }}>
724 A simulated session showing what enters context and what it costs
725 </div>
726 </div>
727 <div style={{
728 textAlign: 'right',
729 flexShrink: 0
730 }}>
731 <div style={{
732 fontFamily: mono,
733 fontSize: 20,
734 fontWeight: 600,
735 color: barColor,
736 letterSpacing: -0.5,
737 lineHeight: 1
738 }}>
739 ~{fmt(totalTokens)}<span style={{
740 fontSize: 15,
741 fontWeight: 500,
742 marginLeft: 4
743 }}>tokens</span>
744 </div>
745 <div style={{
746 fontFamily: mono,
747 fontSize: 13,
748 color: 'var(--cw-text-dim)',
749 marginTop: 2
750 }} title="Token counts are illustrative. Actual values vary with your CLAUDE.md size, MCP servers, and file lengths.">
751 / {fmt(MAX)} · illustrative
752 </div>
753 </div>
754 </div>
755
756 {}
757 <div style={{
758 padding: '0 20px'
759 }}>
760 <div style={{
761 height: 4,
762 borderRadius: 2,
763 background: 'var(--cw-track)',
764 overflow: 'hidden',
765 marginBottom: 6
766 }}>
767 <div style={{
768 width: pct + '%',
769 height: '100%',
770 background: barColor,
771 transition: 'width 0.6s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s'
772 }} />
773 </div>
774 <div style={{
775 height: 28,
776 borderRadius: 5,
777 background: 'var(--cw-track)',
778 border: '1px solid var(--cw-border)',
779 overflow: 'hidden',
780 display: 'flex'
781 }}>
782 {blocks.map((b, i) => {
783 const w = Math.max(b.tokens / MAX * 100, 0.15);
784 const isHov = b.visIdx === activeIdx;
785 const catMatch = hovCat && b.color === hovCat;
786 const dimmed = hovCat ? !catMatch : activeIdx !== null && !isHov;
787 return <div key={b.id} onMouseEnter={() => setHovIdx(b.visIdx)} onMouseLeave={() => setHovIdx(null)} onClick={() => setSelIdx(selIdx === b.visIdx ? null : b.visIdx)} style={{
788 width: w + '%',
789 height: '100%',
790 background: b.color,
791 opacity: isHov || catMatch ? 1 : dimmed ? 0.25 : 0.65,
792 borderRight: i < blocks.length - 1 ? '0.5px solid var(--cw-border)' : 'none',
793 transition: 'opacity 0.15s',
794 cursor: 'pointer'
795 }} />;
796 })}
797 </div>
798 <div style={{
799 display: 'flex',
800 gap: 12,
801 marginTop: 6,
802 flexWrap: 'wrap',
803 justifyContent: 'space-between'
804 }}>
805 <div style={{
806 display: 'flex',
807 gap: 12,
808 flexWrap: 'wrap'
809 }}>
810 {LEGEND.map(x => {
811 const active = hovCat === x.c;
812 return <div key={x.l} onMouseEnter={() => setHovCat(x.c)} onMouseLeave={() => setHovCat(null)} style={{
813 display: 'flex',
814 alignItems: 'center',
815 gap: 4,
816 padding: '2px 6px',
817 borderRadius: 4,
818 cursor: 'pointer',
819 background: active ? 'var(--cw-hover)' : 'transparent',
820 transition: 'background 0.1s'
821 }}>
822 <div style={{
823 width: 6,
824 height: 6,
825 borderRadius: 1.5,
826 background: x.c,
827 opacity: active ? 1 : 0.7
828 }} />
829 <span style={{
830 fontSize: 12,
831 color: active ? 'var(--cw-text)' : 'var(--cw-text-dim)'
832 }}>{x.l}</span>
833 </div>;
834 })}
835 </div>
836 <div style={{
837 display: 'flex',
838 gap: 6,
839 alignItems: 'center',
840 fontSize: 12,
841 color: 'var(--cw-text-dim)'
842 }}>
843 <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#558A42" strokeWidth="2.5">
844 <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /><circle cx="12" cy="12" r="3" />
845 </svg>
846 <span>= appears in your terminal</span>
847 </div>
848 </div>
849 </div>
850
851 {}
852 <div style={{
853 display: 'flex',
854 padding: '14px 20px 0',
855 gap: 16,
856 height: isFullscreen ? 'calc(100vh - 240px)' : 420
857 }}>
858
859 {}
860 <div ref={scrollRef} className="cw-scroll" style={{
861 flex: 1,
862 minWidth: 0,
863 overflowY: 'auto',
864 paddingRight: 8,
865 scrollBehavior: 'smooth'
866 }}>
867 {visible.length === 0 && !playing && <div style={{
868 height: '100%',
869 display: 'flex',
870 flexDirection: 'column',
871 alignItems: 'center',
872 justifyContent: 'center',
873 gap: 16
874 }}>
875 <div style={{
876 fontFamily: mono,
877 fontSize: 16,
878 color: 'var(--cw-text-dim)',
879 display: 'flex',
880 alignItems: 'center',
881 gap: 8
882 }}>
883 <span style={{
884 color: 'var(--cw-text-faint)'
885 }}>$</span>
886 <span>claude</span>
887 <span style={{
888 display: 'inline-block',
889 width: 8,
890 height: 16,
891 background: 'var(--cw-text-dim)',
892 opacity: 0.5,
893 animation: 'cw-blink 1s step-end infinite'
894 }} />
895 </div>
896 <button onClick={() => setPlaying(true)} style={{
897 padding: '10px 20px',
898 borderRadius: 8,
899 border: '1px solid rgba(217,119,87,0.3)',
900 background: 'rgba(217,119,87,0.08)',
901 color: '#D97757',
902 fontSize: 15,
903 fontWeight: 600,
904 cursor: 'pointer',
905 display: 'flex',
906 alignItems: 'center',
907 gap: 8
908 }}>
909 <span>▶</span>
910 <span>Start session</span>
911 </button>
912 <div style={{
913 fontSize: 13,
914 color: 'var(--cw-text-faint)',
915 maxWidth: 280,
916 textAlign: 'center',
917 lineHeight: 1.5
918 }}>
919 Watch what loads into context, from the moment you run <code style={{
920 fontFamily: mono
921 }}>claude</code> through a full conversation.
922 </div>
923 </div>}
924 {isCompacted && <div style={{
925 marginBottom: 10,
926 padding: '10px 12px',
927 borderRadius: 6,
928 background: 'rgba(217,119,87,0.05)',
929 border: '1px solid rgba(217,119,87,0.15)'
930 }}>
931 <div style={{
932 fontSize: 13,
933 fontWeight: 600,
934 color: '#D97757',
935 marginBottom: 3
936 }}>
937 After /compact
938 </div>
939 <div style={{
940 fontSize: 13,
941 color: 'var(--cw-text-3)',
942 lineHeight: 1.5,
943 fontFamily: mono
944 }}>
945 {fmt(preCompactTotal)} → {fmt(totalTokens)} tokens · freed {fmt(preCompactTotal - totalTokens)}
946 </div>
947 <div style={{
948 fontSize: 13,
949 color: 'var(--cw-text-dim)',
950 lineHeight: 1.5,
951 marginTop: 4
952 }}>
953 This is what's left in context: startup content, which lives outside the message history and reloads after compaction, a structured summary of the entire conversation, the files modified most recently, and the body of each skill you invoked. Skill descriptions don't reload.
954 </div>
955 </div>}
956 {time > 0 && visible.length > 0 && <div style={{
957 fontSize: 12,
958 fontWeight: 700,
959 color: 'var(--cw-text-faint)',
960 textTransform: 'uppercase',
961 letterSpacing: 0.6,
962 marginBottom: 6,
963 paddingLeft: 28
964 }}>
965 {isCompacted ? 'Reloaded after compact' : 'Before you type anything'}
966 </div>}
967
968 {time > 0 && visible.map((evt, i) => {
969 const meta = KIND_META[evt.kind];
970 const isHov = hovIdx === i;
971 const prevKind = i > 0 ? visible[i - 1].kind : null;
972 const isSub = evt.kind === 'sub';
973 const enteringSubagent = isSub && prevKind !== 'sub';
974 const leavingSubagent = prevKind === 'sub' && !isSub;
975 let showPhase = null;
976 if (isCompacted && evt.restoredAfterCompact) {
977 if (prevKind === 'compact') showPhase = 'Restored after /compact';
978 } else if (evt.kind === 'user' && prevKind !== 'user') showPhase = 'You'; else if (evt.kind === 'claude' && prevKind === 'user') showPhase = 'Claude works'; else if (evt.label === 'Conversation summary') showPhase = 'Summarized by /compact';
979 const isNewRow = isCompacted && !(evt.kind === 'auto' && evt.t < STARTUP_END);
980 return <div key={evt.label + evt.t} className={isNewRow ? 'cw-compacted-row' : ''} style={isNewRow ? {
981 animationDelay: `${i * 60}ms`
982 } : {}}>
983 {showPhase && <div style={{
984 fontSize: 12,
985 fontWeight: 700,
986 color: 'var(--cw-text-faint)',
987 textTransform: 'uppercase',
988 letterSpacing: 0.6,
989 marginTop: 14,
990 marginBottom: 6,
991 paddingLeft: 28
992 }}>
993 {showPhase}
994 </div>}
995 {enteringSubagent && <div style={{
996 marginLeft: 28,
997 marginTop: 6,
998 marginBottom: 2,
999 paddingLeft: 10,
1000 borderLeft: '2px solid rgba(155,123,196,0.4)',
1001 fontSize: 12,
1002 fontWeight: 600,
1003 color: '#9B7BC4',
1004 textTransform: 'uppercase',
1005 letterSpacing: 0.5
1006 }}>
1007 Subagent's separate context window
1008 </div>}
1009 {leavingSubagent && <div style={{
1010 marginLeft: 28,
1011 marginBottom: 6,
1012 paddingLeft: 10,
1013 paddingBottom: 6,
1014 borderLeft: '2px solid rgba(155,123,196,0.4)',
1015 fontSize: 12,
1016 color: 'var(--cw-text-dim)',
1017 fontFamily: mono
1018 }}>
1019 ↓ {fmt(subTotal)} tokens stayed in subagent's context · only the summary returns
1020 </div>}
1021 <div onMouseEnter={() => setHovIdx(i)} onMouseLeave={() => setHovIdx(null)} onClick={() => setSelIdx(selIdx === i ? null : i)} style={{
1022 display: 'flex',
1023 alignItems: 'flex-start',
1024 borderRadius: 6,
1025 cursor: 'pointer',
1026 background: selIdx === i || isHov ? 'var(--cw-hover)' : 'transparent',
1027 outline: selIdx === i ? '1px solid rgba(217,119,87,0.4)' : 'none',
1028 opacity: hovCat && evt.color !== hovCat ? 0.35 : 1,
1029 transition: 'background 0.1s, opacity 0.15s',
1030 marginLeft: isSub ? 28 : 0,
1031 paddingLeft: isSub ? 10 : 0,
1032 borderLeft: isSub ? '2px solid rgba(155,123,196,0.4)' : 'none'
1033 }}>
1034 <div style={{
1035 width: 28,
1036 display: 'flex',
1037 flexDirection: 'column',
1038 alignItems: 'center',
1039 paddingTop: 8,
1040 flexShrink: 0
1041 }}>
1042 <div style={{
1043 width: evt.kind === 'user' || evt.kind === 'compact' ? 10 : 7,
1044 height: evt.kind === 'user' || evt.kind === 'compact' ? 10 : 7,
1045 borderRadius: '50%',
1046 background: evt.color,
1047 opacity: isHov ? 1 : 0.6,
1048 transition: 'opacity 0.15s',
1049 boxShadow: isHov ? `0 0 8px ${evt.color}40` : 'none'
1050 }} />
1051 {i < visible.length - 1 && <div style={{
1052 width: 1.5,
1053 flex: 1,
1054 background: 'var(--cw-rail)',
1055 marginTop: 2,
1056 minHeight: 6
1057 }} />}
1058 </div>
1059 <div style={{
1060 flex: 1,
1061 minWidth: 0,
1062 padding: '5px 10px 5px 4px',
1063 display: 'flex',
1064 alignItems: 'center',
1065 gap: 8
1066 }}>
1067 <span style={{
1068 fontSize: 12,
1069 fontWeight: 600,
1070 padding: '1px 5px',
1071 borderRadius: 3,
1072 background: meta.badgeBg,
1073 color: meta.badgeColor,
1074 flexShrink: 0,
1075 fontFamily: mono
1076 }}>
1077 {meta.badge}
1078 </span>
1079 <span style={{
1080 fontSize: 15,
1081 fontFamily: mono,
1082 color: isHov ? 'var(--cw-text)' : evt.kind === 'user' ? '#558A42' : evt.kind === 'auto' ? 'var(--cw-text-dim)' : 'var(--cw-text-2)',
1083 flex: 1,
1084 minWidth: 0,
1085 overflow: 'hidden',
1086 textOverflow: 'ellipsis',
1087 whiteSpace: 'nowrap',
1088 fontWeight: evt.kind === 'user' ? 550 : 400
1089 }}>
1090 {evt.label}
1091 </span>
1092 {evt.tokens > 0 && <span style={{
1093 fontSize: 12,
1094 fontFamily: mono,
1095 color: 'var(--cw-text-faint)',
1096 flexShrink: 0
1097 }}>
1098 +{fmt(evt.tokens)}
1099 </span>}
1100 {evt.subTokens > 0 && <span style={{
1101 fontSize: 12,
1102 fontFamily: mono,
1103 color: '#9B7BC4',
1104 flexShrink: 0,
1105 opacity: 0.6
1106 }}>
1107 +{fmt(evt.subTokens)}
1108 </span>}
1109 {evt.tokens > 0 && <div style={{
1110 width: 50,
1111 height: 5,
1112 borderRadius: 2,
1113 background: 'var(--cw-track)',
1114 flexShrink: 0,
1115 overflow: 'hidden'
1116 }}>
1117 <div style={{
1118 width: Math.min(evt.tokens / 5000 * 100, 100) + '%',
1119 height: '100%',
1120 background: evt.color,
1121 opacity: isHov ? 0.8 : 0.4,
1122 transition: 'opacity 0.15s'
1123 }} />
1124 </div>}
1125 <span style={{
1126 width: 14,
1127 flexShrink: 0,
1128 display: 'flex',
1129 justifyContent: 'center'
1130 }} title={VIS_META[evt.vis].label}>
1131 {evt.vis !== 'hidden' && <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke={evt.vis === 'full' ? '#558A42' : 'currentColor'} style={{
1132 color: 'var(--cw-text-faint)',
1133 opacity: evt.vis === 'full' ? 1 : 0.5
1134 }} strokeWidth="2">
1135 <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /><circle cx="12" cy="12" r="3" />
1136 </svg>}
1137 </span>
1138 </div>
1139 </div>
1140 </div>;
1141 })}
1142
1143 {activeGate && (activeGate.kind === 'prompt' || activeGate.kind === 'bang' || activeGate.kind === 'slash') && <div style={{
1144 paddingLeft: 28,
1145 marginTop: 12,
1146 paddingRight: 8
1147 }}>
1148 <div style={{
1149 fontSize: 11,
1150 fontWeight: 600,
1151 color: '#6BA656',
1152 fontFamily: mono,
1153 textTransform: 'uppercase',
1154 letterSpacing: 0.5,
1155 marginBottom: 4,
1156 paddingLeft: 2
1157 }}>
1158 You type in your terminal
1159 </div>
1160 <div style={{
1161 display: 'flex',
1162 alignItems: 'flex-start',
1163 gap: 8,
1164 padding: '10px 12px',
1165 borderRadius: 6,
1166 background: 'rgba(85,138,66,0.06)',
1167 border: '1px solid rgba(85,138,66,0.2)'
1168 }}>
1169 <span style={{
1170 color: '#558A42',
1171 fontSize: 15,
1172 fontFamily: mono,
1173 flexShrink: 0
1174 }}>❯</span>
1175 <span style={{
1176 fontSize: 15,
1177 fontFamily: mono,
1178 color: 'var(--cw-text-2)',
1179 flex: 1,
1180 lineHeight: 1.5
1181 }}>
1182 {activeGate.text}
1183 <span style={{
1184 display: 'inline-block',
1185 width: 7,
1186 height: 13,
1187 marginLeft: 2,
1188 background: '#558A42',
1189 opacity: 0.5,
1190 verticalAlign: 'middle',
1191 animation: 'cw-blink 1s step-end infinite'
1192 }} />
1193 </span>
1194 <button onClick={sendPrompt} style={{
1195 padding: '5px 12px',
1196 borderRadius: 5,
1197 border: 'none',
1198 background: '#558A42',
1199 color: '#fff',
1200 fontSize: 13,
1201 fontWeight: 600,
1202 cursor: 'pointer',
1203 flexShrink: 0
1204 }}>
1205 {activeGate.kind === 'prompt' ? 'Send ↵' : 'Run ↵'}
1206 </button>
1207 </div>
1208 </div>}
1209 {activeGate && activeGate.kind === 'compact' && <div style={{
1210 paddingLeft: 28,
1211 marginTop: 12,
1212 paddingRight: 8
1213 }}>
1214 <div style={{
1215 padding: '12px 14px',
1216 borderRadius: 6,
1217 background: 'rgba(217,119,87,0.06)',
1218 border: '1px solid rgba(217,119,87,0.25)'
1219 }}>
1220 <div style={{
1221 fontSize: 13,
1222 color: 'var(--cw-text-3)',
1223 marginBottom: 8,
1224 lineHeight: 1.5
1225 }}>
1226 Context is at <span style={{
1227 fontFamily: mono,
1228 fontWeight: 600,
1229 color: barColor
1230 }}>{fmt(totalTokens)} tokens</span>.
1231 Run <code style={{
1232 fontFamily: mono,
1233 background: 'var(--cw-track)',
1234 padding: '1px 4px',
1235 borderRadius: 3
1236 }}>/compact</code> to
1237 summarize older exchanges and free space for more work.
1238 </div>
1239 <div style={{
1240 display: 'flex',
1241 alignItems: 'center',
1242 gap: 8
1243 }}>
1244 <span style={{
1245 color: '#D97757',
1246 fontSize: 15,
1247 fontFamily: mono
1248 }}>❯</span>
1249 <span style={{
1250 fontSize: 15,
1251 fontFamily: mono,
1252 color: 'var(--cw-text-2)',
1253 flex: 1
1254 }}>
1255 {activeGate.text}
1256 </span>
1257 <button onClick={sendPrompt} style={{
1258 padding: '5px 12px',
1259 borderRadius: 5,
1260 border: 'none',
1261 background: '#D97757',
1262 color: '#fff',
1263 fontSize: 13,
1264 fontWeight: 600,
1265 cursor: 'pointer',
1266 flexShrink: 0
1267 }}>
1268 Run ↵
1269 </button>
1270 </div>
1271 </div>
1272 </div>}
1273 </div>
1274
1275 {}
1276 <div style={{
1277 width: 300,
1278 flexShrink: 0,
1279 display: 'flex',
1280 flexDirection: 'column'
1281 }}>
1282 <div ref={detailRef} className="cw-scroll" style={{
1283 padding: '14px 16px',
1284 borderRadius: 10,
1285 background: 'var(--cw-surface)',
1286 border: '1px solid var(--cw-border)',
1287 flex: 1,
1288 minHeight: 0,
1289 overflowY: 'auto',
1290 display: 'flex',
1291 flexDirection: 'column',
1292 gap: 10
1293 }}>
1294 {hovEvent ? <div>
1295 <div style={{
1296 display: 'flex',
1297 alignItems: 'center',
1298 gap: 8,
1299 marginBottom: 8
1300 }}>
1301 <div style={{
1302 width: 10,
1303 height: 10,
1304 borderRadius: 3,
1305 background: hovEvent.color,
1306 opacity: 0.8
1307 }} />
1308 <span style={{
1309 fontSize: 16,
1310 fontWeight: 600
1311 }}>{hovEvent.label}</span>
1312 </div>
1313 <div style={{
1314 display: 'flex',
1315 width: 'fit-content',
1316 padding: '3px 8px',
1317 borderRadius: 4,
1318 marginBottom: 8,
1319 background: KIND_META[hovEvent.kind].badgeBg
1320 }}>
1321 <span style={{
1322 fontSize: 12,
1323 fontWeight: 600,
1324 color: KIND_META[hovEvent.kind].badgeColor
1325 }}>
1326 {KIND_META[hovEvent.kind].detail}
1327 </span>
1328 </div>
1329 {hovEvent.tokens > 0 && <div style={{
1330 fontSize: 14,
1331 fontFamily: mono,
1332 color: 'var(--cw-text-dim)',
1333 marginBottom: 6
1334 }}>
1335 {fmt(hovEvent.tokens)} tokens
1336 </div>}
1337 {hovEvent.subTokens > 0 && <div style={{
1338 fontSize: 14,
1339 fontFamily: mono,
1340 color: '#9B7BC4',
1341 marginBottom: 6
1342 }}>
1343 {fmt(hovEvent.subTokens)} tokens in the subagent's context
1344 </div>}
1345 <p style={{
1346 fontSize: 15,
1347 color: 'var(--cw-text-3)',
1348 lineHeight: 1.55,
1349 margin: 0
1350 }}>
1351 {renderWithCode(hovEvent.desc)}
1352 </p>
1353 <div style={{
1354 marginTop: 10,
1355 padding: '8px 10px',
1356 borderRadius: 6,
1357 background: hovEvent.vis === 'full' ? 'rgba(85,138,66,0.08)' : 'var(--cw-surface-2)',
1358 border: '1px solid ' + (hovEvent.vis === 'full' ? 'rgba(85,138,66,0.2)' : 'var(--cw-border)')
1359 }}>
1360 <div style={{
1361 display: 'flex',
1362 alignItems: 'center',
1363 gap: 6,
1364 marginBottom: 3
1365 }}>
1366 <span style={{
1367 fontSize: 13,
1368 color: hovEvent.vis === 'full' ? '#558A42' : 'var(--cw-text-dim)'
1369 }}>
1370 {hovEvent.vis === 'full' ? '●' : hovEvent.vis === 'brief' ? '◐' : '○'}
1371 </span>
1372 <span style={{
1373 fontSize: 12,
1374 fontWeight: 600,
1375 color: 'var(--cw-text-2)'
1376 }}>
1377 {VIS_META[hovEvent.vis].label}
1378 </span>
1379 </div>
1380 <div style={{
1381 fontSize: 13,
1382 color: 'var(--cw-text-dim)',
1383 lineHeight: 1.4
1384 }}>
1385 {VIS_META[hovEvent.vis].sub}
1386 </div>
1387 </div>
1388 {hovEvent.tip && <div style={{
1389 marginTop: 10,
1390 padding: '8px 10px',
1391 borderRadius: 6,
1392 background: 'rgba(85,138,66,0.06)',
1393 border: '1px solid rgba(85,138,66,0.15)'
1394 }}>
1395 <div style={{
1396 fontSize: 12,
1397 fontWeight: 600,
1398 color: '#558A42',
1399 marginBottom: 3,
1400 display: 'flex',
1401 alignItems: 'center',
1402 gap: 4
1403 }}>
1404 <span>💡</span> Save context
1405 </div>
1406 <div style={{
1407 fontSize: 13,
1408 color: 'var(--cw-text-3)',
1409 lineHeight: 1.5
1410 }}>
1411 {renderWithCode(hovEvent.tip)}
1412 </div>
1413 </div>}
1414 {hovEvent.link && <a href={hovEvent.link} style={{
1415 display: 'inline-block',
1416 marginTop: 10,
1417 fontSize: 13,
1418 color: '#D97757',
1419 textDecoration: 'none',
1420 borderBottom: '1px solid rgba(217,119,87,0.3)'
1421 }}>
1422 Learn more →
1423 </a>}
1424 </div> : <div style={{
1425 display: 'flex',
1426 flexDirection: 'column',
1427 alignItems: 'center',
1428 textAlign: 'center',
1429 gap: 4,
1430 padding: '12px 0 4px'
1431 }}>
1432 <div style={{
1433 fontSize: 22,
1434 opacity: 0.2
1435 }}>👁</div>
1436 <div style={{
1437 fontSize: 14,
1438 fontWeight: 500,
1439 color: 'var(--cw-text-dim)'
1440 }}>Hover or click any event</div>
1441 <div style={{
1442 fontSize: 12,
1443 color: 'var(--cw-text-faint)',
1444 lineHeight: 1.4,
1445 maxWidth: 200
1446 }}>
1447 Hover to preview. Click to pin so you can scroll.
1448 </div>
1449 </div>}
1450
1451 <div style={{
1452 padding: '10px 12px',
1453 borderRadius: 8,
1454 background: 'rgba(217,119,87,0.05)',
1455 border: '1px solid rgba(217,119,87,0.12)'
1456 }}>
1457 <div style={{
1458 fontSize: 11,
1459 fontWeight: 700,
1460 color: '#D97757',
1461 textTransform: 'uppercase',
1462 letterSpacing: 0.5,
1463 marginBottom: 3
1464 }}>
1465 Key takeaway
1466 </div>
1467 <div style={{
1468 fontSize: 13,
1469 color: 'var(--cw-text-3)',
1470 lineHeight: 1.5
1471 }}>
1472 {takeaway}
1473 </div>
1474 </div>
1475
1476 <div style={{
1477 padding: '10px 12px',
1478 borderRadius: 8,
1479 background: 'var(--cw-surface-2)',
1480 border: '1px solid var(--cw-border)'
1481 }}>
1482 <div style={{
1483 fontSize: 11,
1484 fontWeight: 700,
1485 color: 'var(--cw-text-dim)',
1486 textTransform: 'uppercase',
1487 letterSpacing: 0.5,
1488 marginBottom: 3
1489 }}>
1490 In your terminal you see
1491 </div>
1492 <div style={{
1493 fontSize: 13,
1494 color: 'var(--cw-text-3)',
1495 lineHeight: 1.5
1496 }}>
1497 {terminalView}
1498 </div>
1499 </div>
1500 </div>
1501 </div>
1502 </div>
1503
1504 {}
1505 <div style={{
1506 padding: '10px 20px 14px',
1507 display: 'flex',
1508 alignItems: 'center',
1509 gap: 10
1510 }}>
1511 <button aria-label={time >= 1 ? 'Restart' : activeGate ? 'Continue' : playing ? 'Pause' : 'Play'} onClick={() => {
1512 if (time >= 1) {
1513 setTime(0);
1514 setGatesPassed(0);
1515 setSelIdx(null);
1516 setHovIdx(null);
1517 setPlaying(true);
1518 } else if (activeGate) sendPrompt(); else setPlaying(!playing);
1519 }} style={{
1520 width: 30,
1521 height: 30,
1522 borderRadius: 6,
1523 border: 'none',
1524 background: 'rgba(217,119,87,0.1)',
1525 color: '#D97757',
1526 cursor: 'pointer',
1527 fontSize: 15,
1528 fontWeight: 700,
1529 display: 'flex',
1530 alignItems: 'center',
1531 justifyContent: 'center'
1532 }}>
1533 {time >= 1 ? '↺' : playing ? '⏸' : '▶'}
1534 </button>
1535 <div style={{
1536 flex: 1,
1537 height: 3,
1538 borderRadius: 2,
1539 background: 'var(--cw-track)',
1540 overflow: 'hidden'
1541 }}>
1542 <div style={{
1543 width: time * 100 + '%',
1544 height: '100%',
1545 background: '#D97757',
1546 transition: 'width 0.1s linear'
1547 }} />
1548 </div>
1549 <span style={{
1550 fontSize: 12,
1551 fontFamily: mono,
1552 color: 'var(--cw-text-faint)',
1553 minWidth: 30
1554 }}>
1555 {Math.round(time * 100)}%
1556 </span>
1557 <button onClick={toggleFullscreen} aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'} title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'} style={{
1558 width: 28,
1559 height: 28,
1560 borderRadius: 6,
1561 border: '1px solid var(--cw-border)',
1562 background: 'var(--cw-surface)',
1563 color: 'var(--cw-text-dim)',
1564 cursor: 'pointer',
1565 fontSize: 15,
1566 flexShrink: 0,
1567 marginLeft: 4,
1568 display: 'flex',
1569 alignItems: 'center',
1570 justifyContent: 'center'
1571 }}>
1572 {isFullscreen ? '⤡' : '⛶'}
1573 </button>
1574 </div>
1575 </div>
1576 </>;
1577};
1578
1579La fenêtre de contexte de Claude Code contient tout ce que Claude sait sur votre session : vos instructions, les fichiers qu'il lit, ses propres réponses, et le contenu qui n'apparaît jamais dans votre terminal. La chronologie ci-dessous joue une session complète du démarrage à la compaction : ce qui se charge avant que vous ne tapiez, ce que chaque lecture de fichier, règle et hook ajoute pendant que Claude travaille, et comment un sous-agent garde les lectures volumineuses en dehors de votre contexte. Consultez [la description écrite](#what-the-timeline-shows) pour le même contenu sous forme de liste.9La fenêtre de contexte de Claude Code contient tout ce que Claude sait sur votre session : vos instructions, les fichiers qu'il lit, ses propres réponses, et le contenu qui n'apparaît jamais dans votre terminal. La chronologie ci-dessous joue une session complète du démarrage à la compaction : ce qui se charge avant que vous ne tapiez, ce que chaque lecture de fichier, règle et hook ajoute pendant que Claude travaille, et comment un sous-agent garde les lectures volumineuses en dehors de votre contexte. Consultez [la description écrite](#what-the-timeline-shows) pour le même contenu sous forme de liste.
1580 10
1581<ContextWindow />11## Ce que la chronologie montre
1582
1583<h2 id="what-the-timeline-shows">
1584 Ce que la chronologie montre
1585</h2>
1586 12
1587La session parcourt un flux réaliste avec des comptages de jetons représentatifs :13La session parcourt un flux réaliste avec des comptages de jetons représentatifs :
1588 14
1591* **L'invite de suivi** : un [sous-agent](/docs/fr/sub-agents) gère la recherche dans sa propre fenêtre de contexte séparée, de sorte que les lectures de fichiers volumineux restent en dehors de la vôtre. Seul le résumé et une petite remorque de métadonnées reviennent.17* **L'invite de suivi** : un [sous-agent](/docs/fr/sub-agents) gère la recherche dans sa propre fenêtre de contexte séparée, de sorte que les lectures de fichiers volumineux restent en dehors de la vôtre. Seul le résumé et une petite remorque de métadonnées reviennent.
1592* **À la fin de la présentation** : vous exécutez `/compact`, qui remplace la conversation par un résumé structuré. La plupart du contenu de démarrage se recharge automatiquement ; le tableau ci-dessous montre ce qui se passe pour chaque mécanisme.18* **À la fin de la présentation** : vous exécutez `/compact`, qui remplace la conversation par un résumé structuré. La plupart du contenu de démarrage se recharge automatiquement ; le tableau ci-dessous montre ce qui se passe pour chaque mécanisme.
1593 19
1594<h2 id="what-survives-compaction">20## Ce qui survit à la compaction
1595 Ce qui survit à la compaction
1596</h2>
1597 21
1598Quand une longue session se compacte, Claude Code résume l'historique de la conversation pour l'adapter à la fenêtre de contexte. À partir de la v2.1.198, la requête de résumé hérite de la configuration de [réflexion étendue](/docs/fr/model-config#extended-thinking) de votre session, de sorte qu'elle raisonne avec la réflexion activée quand votre session l'a activée et reste désactivée sinon. La réflexion affecte uniquement la façon dont le résumé est produit ; vos paramètres de session restent inchangés après. Ce qui arrive à chaque type de contenu dépend de la façon dont il a été chargé :22Quand une longue session se compacte, Claude Code résume l'historique de la conversation pour l'adapter à la fenêtre de contexte. À partir de la v2.1.198, la requête de résumé hérite de la configuration de [réflexion étendue](/docs/fr/model-config#extended-thinking) de votre session, de sorte qu'elle raisonne avec la réflexion activée quand votre session l'a activée et reste désactivée sinon. La réflexion affecte uniquement la façon dont le résumé est produit ; vos paramètres de session restent inchangés après. Ce qui arrive à chaque type de contenu dépend de la façon dont il a été chargé :
1599 23
1602| Prompt système et style de sortie | Les deux s'appliquent toujours |26| Prompt système et style de sortie | Les deux s'appliquent toujours |
1603| CLAUDE.md à la racine du projet et règles sans portée | Réinjectés depuis le disque |27| CLAUDE.md à la racine du projet et règles sans portée | Réinjectés depuis le disque |
1604| Mémoire automatique | Réinjectée depuis le disque |28| Mémoire automatique | Réinjectée depuis le disque |
29| Descriptions des skills | Reconstruites à partir de vos [skills](/docs/fr/skills) actuels et réinjectées. Nécessite Claude Code v2.1.304 ou ultérieure |
1605| [Snapshot du statut Git](/docs/fr/settings-reference#includegitinstructions) | Si la session a démarré avec un snapshot, Claude Code en lit un nouveau depuis votre dépôt |30| [Snapshot du statut Git](/docs/fr/settings-reference#includegitinstructions) | Si la session a démarré avec un snapshot, Claude Code en lit un nouveau depuis votre dépôt |
1606| Le plan que Claude a écrit en [mode plan](/docs/fr/permission-modes#analyze-before-you-edit-with-plan-mode) | Réinjecté depuis le disque |31| Le plan que Claude a écrit en [mode plan](/docs/fr/permission-modes#analyze-before-you-edit-with-plan-mode) | Réinjecté depuis le disque |
1607| Règles avec frontmatter `paths:` | Claude Code les recharge [à la demande](/docs/fr/memory#path-specific-rules) |32| Règles avec frontmatter `paths:` | Claude Code les recharge [à la demande](/docs/fr/memory#path-specific-rules) |
1618 43
1619Les corps des skills sont réinjectés après compaction, mais les skills volumineux sont tronqués pour s'adapter au plafond par skill, et les skills invoqués les plus anciens sont supprimés une fois le budget total dépassé. La troncature conserve le début du fichier, donc mettez les instructions les plus importantes près du haut de `SKILL.md`.44Les corps des skills sont réinjectés après compaction, mais les skills volumineux sont tronqués pour s'adapter au plafond par skill, et les skills invoqués les plus anciens sont supprimés une fois le budget total dépassé. La troncature conserve le début du fichier, donc mettez les instructions les plus importantes près du haut de `SKILL.md`.
1620 45
1621<h2 id="when-your-context-fills-up">46## Quand votre contexte se remplit
1622 Quand votre contexte se remplit
1623</h2>
1624 47
1625Claude Code se compacte automatiquement à mesure que vous approchez de la limite, de sorte qu'une fenêtre de contexte complète ne termine pas votre session. La passe automatique fonctionne de la même manière que l'étape `/compact` dans la chronologie. Consultez [Quand le contexte se remplit](/docs/fr/how-claude-code-works#when-context-fills-up) pour voir ce qu'elle préserve. Pour savoir quand Claude Code compacte également une conversation que vous laissez inactive, consultez [compaction en cas d'inactivité](/docs/fr/settings-reference#idlecompaction).48Claude Code se compacte automatiquement à mesure que vous approchez de la limite, de sorte qu'une fenêtre de contexte complète ne termine pas votre session. La passe automatique fonctionne de la même manière que l'étape `/compact` dans la chronologie. Consultez [Quand le contexte se remplit](/docs/fr/how-claude-code-works#when-context-fills-up) pour voir ce qu'elle préserve. Pour savoir quand Claude Code compacte également une conversation que vous laissez inactive, consultez [compaction en cas d'inactivité](/docs/fr/settings-reference#idlecompaction).
1626 49
1636 59
1637Le point où la compaction automatique s'exécute dépend de votre modèle et de votre configuration. Consultez [Default auto-compact thresholds](/docs/fr/model-config#default-auto-compact-thresholds) pour les limites par modèle, et [Correct the window for a gateway or custom model ID](/docs/fr/model-config#correct-the-window-for-a-gateway-or-custom-model-id) si Claude Code suppose la mauvaise fenêtre pour votre ID de modèle, comme un alias de [passerelle LLM](/docs/fr/llm-gateway).60Le point où la compaction automatique s'exécute dépend de votre modèle et de votre configuration. Consultez [Default auto-compact thresholds](/docs/fr/model-config#default-auto-compact-thresholds) pour les limites par modèle, et [Correct the window for a gateway or custom model ID](/docs/fr/model-config#correct-the-window-for-a-gateway-or-custom-model-id) si Claude Code suppose la mauvaise fenêtre pour votre ID de modèle, comme un alias de [passerelle LLM](/docs/fr/llm-gateway).
1638 61
1639<h2 id="check-your-own-session">62## Vérifiez votre propre session
1640 Vérifiez votre propre session
1641</h2>
1642 63
1643La visualisation utilise des nombres représentatifs. Pour voir votre utilisation réelle du contexte à tout moment, exécutez `/context` pour une ventilation en direct par catégorie avec des suggestions d'optimisation, y compris les fichiers CLAUDE.md et de mémoire automatique chargés. Exécutez `/memory` pour ouvrir et modifier ces fichiers.64La visualisation utilise des nombres représentatifs. Pour voir votre utilisation réelle du contexte à tout moment, exécutez `/context` pour une ventilation en direct par catégorie avec des suggestions d'optimisation, y compris les fichiers CLAUDE.md et de mémoire automatique chargés. Exécutez `/memory` pour ouvrir et modifier ces fichiers.
1644 65
1645<h2 id="related-resources">66## Ressources connexes
1646 Ressources connexes
1647</h2>
1648 67
1649Pour une couverture plus approfondie des fonctionnalités présentées dans la chronologie, consultez ces pages :68Pour une couverture plus approfondie des fonctionnalités présentées dans la chronologie, consultez ces pages :
1650 69