tokenol 0.3.2__tar.gz → 0.4.0__tar.gz

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.
Files changed (106) hide show
  1. {tokenol-0.3.2 → tokenol-0.4.0}/.gitignore +1 -0
  2. {tokenol-0.3.2 → tokenol-0.4.0}/CHANGELOG.md +55 -0
  3. {tokenol-0.3.2 → tokenol-0.4.0}/PKG-INFO +31 -5
  4. {tokenol-0.3.2 → tokenol-0.4.0}/README.md +28 -3
  5. tokenol-0.4.0/docs/superpowers/plans/2026-05-02-persistent-history-pr1.md +3270 -0
  6. tokenol-0.4.0/docs/superpowers/plans/2026-05-03-cold-start-bench.md +822 -0
  7. tokenol-0.4.0/docs/superpowers/plans/2026-05-03-opt-in-persistence.md +1050 -0
  8. tokenol-0.4.0/docs/superpowers/specs/2026-05-02-persistent-history-design.md +273 -0
  9. tokenol-0.4.0/docs/superpowers/specs/2026-05-03-cold-start-bench-design.md +107 -0
  10. tokenol-0.4.0/docs/superpowers/specs/2026-05-03-opt-in-persistence-design.md +261 -0
  11. {tokenol-0.3.2 → tokenol-0.4.0}/pyproject.toml +4 -2
  12. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/__init__.py +1 -1
  13. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/cli.py +22 -0
  14. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/ingest/discovery.py +31 -0
  15. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/model/events.py +1 -0
  16. tokenol-0.4.0/src/tokenol/persistence/__init__.py +1 -0
  17. tokenol-0.4.0/src/tokenol/persistence/flusher.py +112 -0
  18. tokenol-0.4.0/src/tokenol/persistence/forget_handoff.py +114 -0
  19. tokenol-0.4.0/src/tokenol/persistence/store.py +528 -0
  20. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/app.py +128 -3
  21. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/prefs.py +4 -0
  22. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/session_detail.py +2 -0
  23. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/state.py +223 -19
  24. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/breakdown.js +1 -0
  25. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/session.html +7 -0
  26. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/session.js +17 -7
  27. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/streaming.py +103 -1
  28. tokenol-0.4.0/tests/test_ingest_discovery.py +52 -0
  29. tokenol-0.4.0/tests/test_persistence_flusher.py +87 -0
  30. tokenol-0.4.0/tests/test_persistence_forget_handoff.py +69 -0
  31. tokenol-0.4.0/tests/test_persistence_store.py +436 -0
  32. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_serve_app.py +82 -0
  33. tokenol-0.4.0/tests/test_serve_app_no_persist.py +96 -0
  34. tokenol-0.4.0/tests/test_serve_archived_session.py +95 -0
  35. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_serve_prefs.py +23 -2
  36. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_serve_state.py +142 -1
  37. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_serve_streaming.py +166 -0
  38. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_session_detail.py +18 -0
  39. {tokenol-0.3.2 → tokenol-0.4.0}/uv.lock +6 -4
  40. {tokenol-0.3.2 → tokenol-0.4.0}/.claude/settings.local.json +0 -0
  41. {tokenol-0.3.2 → tokenol-0.4.0}/.claudesignore +0 -0
  42. {tokenol-0.3.2 → tokenol-0.4.0}/.github/workflows/ci.yml +0 -0
  43. {tokenol-0.3.2 → tokenol-0.4.0}/docs/ASSUMPTIONS.md +0 -0
  44. {tokenol-0.3.2 → tokenol-0.4.0}/docs/METRICS.md +0 -0
  45. {tokenol-0.3.2 → tokenol-0.4.0}/docs/screenshots/breakdown_bottom.jpg +0 -0
  46. {tokenol-0.3.2 → tokenol-0.4.0}/docs/screenshots/breakdown_top.jpg +0 -0
  47. {tokenol-0.3.2 → tokenol-0.4.0}/docs/screenshots/main.jpg +0 -0
  48. {tokenol-0.3.2 → tokenol-0.4.0}/docs/screenshots/project.jpg +0 -0
  49. {tokenol-0.3.2 → tokenol-0.4.0}/docs/screenshots/session_bottom.jpg +0 -0
  50. {tokenol-0.3.2 → tokenol-0.4.0}/docs/screenshots/session_top.jpg +0 -0
  51. {tokenol-0.3.2 → tokenol-0.4.0}/docs/superpowers/plans/2026-04-24-breakdown-pr1.md +0 -0
  52. {tokenol-0.3.2 → tokenol-0.4.0}/docs/superpowers/plans/2026-04-24-breakdown-pr2.md +0 -0
  53. {tokenol-0.3.2 → tokenol-0.4.0}/docs/superpowers/plans/2026-04-24-breakdown-pr3.md +0 -0
  54. {tokenol-0.3.2 → tokenol-0.4.0}/docs/superpowers/specs/2026-04-24-breakdown-tab-design.md +0 -0
  55. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/assumptions.py +0 -0
  56. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/enums.py +0 -0
  57. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/ingest/__init__.py +0 -0
  58. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/ingest/builder.py +0 -0
  59. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/ingest/parser.py +0 -0
  60. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/ingest/schema.py +0 -0
  61. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/__init__.py +0 -0
  62. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/context.py +0 -0
  63. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/cost.py +0 -0
  64. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/history.py +0 -0
  65. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/patterns.py +0 -0
  66. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/rollups.py +0 -0
  67. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/thresholds.py +0 -0
  68. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/verdicts.py +0 -0
  69. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/metrics/windows.py +0 -0
  70. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/model/__init__.py +0 -0
  71. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/model/pricing.py +0 -0
  72. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/model/registry.py +0 -0
  73. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/report/__init__.py +0 -0
  74. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/report/text.py +0 -0
  75. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/__init__.py +0 -0
  76. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/app.js +0 -0
  77. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/breakdown.html +0 -0
  78. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/chart.js +0 -0
  79. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/components.js +0 -0
  80. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/day.html +0 -0
  81. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/day.js +0 -0
  82. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/index.html +0 -0
  83. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/model.html +0 -0
  84. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/model.js +0 -0
  85. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/project.html +0 -0
  86. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/project.js +0 -0
  87. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/styles.css +0 -0
  88. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/tool.html +0 -0
  89. {tokenol-0.3.2 → tokenol-0.4.0}/src/tokenol/serve/static/tool.js +0 -0
  90. {tokenol-0.3.2 → tokenol-0.4.0}/tests/conftest.py +0 -0
  91. {tokenol-0.3.2 → tokenol-0.4.0}/tests/fixtures/basic.jsonl +0 -0
  92. {tokenol-0.3.2 → tokenol-0.4.0}/tests/fixtures/dedup.jsonl +0 -0
  93. {tokenol-0.3.2 → tokenol-0.4.0}/tests/fixtures/gemini.jsonl +0 -0
  94. {tokenol-0.3.2 → tokenol-0.4.0}/tests/fixtures/interrupted.jsonl +0 -0
  95. {tokenol-0.3.2 → tokenol-0.4.0}/tests/fixtures/missing_ids.jsonl +0 -0
  96. {tokenol-0.3.2 → tokenol-0.4.0}/tests/fixtures/multi.jsonl +0 -0
  97. {tokenol-0.3.2 → tokenol-0.4.0}/tests/fixtures/sidechain.jsonl +0 -0
  98. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_cli.py +0 -0
  99. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_context_metrics.py +0 -0
  100. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_dedup.py +0 -0
  101. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_history_and_thresholds.py +0 -0
  102. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_metrics.py +0 -0
  103. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_parser.py +0 -0
  104. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_patterns.py +0 -0
  105. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_verdicts.py +0 -0
  106. {tokenol-0.3.2 → tokenol-0.4.0}/tests/test_windows.py +0 -0
@@ -1,4 +1,5 @@
1
1
  _local/
2
+ .worktrees/
2
3
  *_PLAN.md
3
4
  *.plan.md
4
5
  RESUME.md
@@ -4,6 +4,61 @@ All notable changes to tokenol are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 0.4.0 — 2026-05-03
8
+
9
+ ### Features
10
+ - **Persistent history that survives JSONL deletion** (opt-in via
11
+ `--persist`). `tokenol serve --persist` backs the live in-memory
12
+ dashboard with a single-file DuckDB store at `~/.tokenol/history.duckdb`
13
+ (override via `TOKENOL_HISTORY_PATH`). On startup the store seeds the
14
+ in-memory hot tier so cold start is bounded by `hot_window_days`
15
+ (default 90), not by total history length. Each tick parses only JSONLs
16
+ whose `mtime_ns` exceeds the per-session high-water mark — typically
17
+ just today's active files — and appends derived turns to both memory and
18
+ a background batch flush (every 30 s or 100 turns). Deleting a JSONL no
19
+ longer drops its data from the dashboard; the affected sessions are
20
+ marked `archived=True` and continue to render every quantitative panel.
21
+ Only the per-turn modal's content snippets (user prompt, assistant
22
+ preview, tool-call list) become unavailable for archived sessions, in
23
+ line with the privacy intent of the deletion. Default off — `tokenol
24
+ serve` without `--persist` matches the v0.3.2 resource profile
25
+ byte-for-byte (no `import duckdb`, no `~/.tokenol/` directory, no extra
26
+ steady RSS).
27
+ - `Preferences.hot_window_days` (default `90`, accepted range `1..3650`),
28
+ exposed via the existing `/api/prefs` endpoint. Takes effect on next
29
+ startup.
30
+ - `Session.archived: bool` field surfaced through `/api/session/{id}` and
31
+ `/api/session/{id}/turn/{idx}`; the session-detail page renders an
32
+ amber "Archived — text snippets unavailable" badge and hides the
33
+ per-turn snippet block when the flag is set.
34
+ - `tokenol.persistence.forget_handoff` — a pidfile + atomic request-file
35
+ handshake so a future `tokenol forget` CLI (PR 2) can apply deletions
36
+ to a live serve within one tick, without requiring a restart.
37
+
38
+ ### Changes
39
+ - `duckdb` moved from a core dependency to the new `[persist]` optional
40
+ extras group. Default `pip install tokenol` no longer pulls the DuckDB
41
+ binary wheel (~30 MB saved). Users who pass `--persist` install with
42
+ `pip install 'tokenol[persist]'`.
43
+ - `build_snapshot_full` now accepts optional `history_store` and
44
+ `flush_queue` arguments. When neither is supplied the legacy whole-corpus
45
+ derivation path is used unchanged, so CLI report commands and any
46
+ existing test that constructs a bare `ParseCache` keep working.
47
+ - Default mode prints a yellow `WARNING` at startup if it finds an existing
48
+ `~/.tokenol/history.duckdb` (or `TOKENOL_HISTORY_PATH`), prompting the
49
+ user to pass `--persist` if they want to use it (rather than silently
50
+ ignoring the file).
51
+ - `select_edge_paths` now tracks per-file `mtime_ns` instead of comparing
52
+ filesystem mtime to turn timestamps — fixes a freshness bug in the
53
+ store-backed snapshot path where backdated turn timestamps could silently
54
+ exclude files from re-parse.
55
+
56
+ ### Notes
57
+ - See `docs/superpowers/specs/2026-05-03-opt-in-persistence-design.md` for
58
+ the gating-and-extras design.
59
+ - See `docs/superpowers/specs/2026-05-02-persistent-history-design.md` for
60
+ the underlying store design.
61
+
7
62
  ## [0.3.2] — 2026-04-28
8
63
 
9
64
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tokenol
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Audit Claude Code JSONL session logs: track cost, cache health, context blow-ups, and 5h-window pressure.
5
5
  Project-URL: Homepage, https://github.com/farhanferoz/tokenol
6
6
  Project-URL: Issues, https://github.com/farhanferoz/tokenol/issues
@@ -15,7 +15,6 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Topic :: Utilities
17
17
  Requires-Python: >=3.10
18
- Requires-Dist: duckdb>=0.10
19
18
  Requires-Dist: rich>=13
20
19
  Requires-Dist: typer>=0.12
21
20
  Provides-Extra: dev
@@ -25,6 +24,8 @@ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
25
24
  Requires-Dist: pytest-cov; extra == 'dev'
26
25
  Requires-Dist: pytest>=8; extra == 'dev'
27
26
  Requires-Dist: ruff>=0.4; extra == 'dev'
27
+ Provides-Extra: persist
28
+ Requires-Dist: duckdb>=0.10; extra == 'persist'
28
29
  Provides-Extra: serve
29
30
  Requires-Dist: fastapi>=0.111; extra == 'serve'
30
31
  Requires-Dist: uvicorn[standard]>=0.29; extra == 'serve'
@@ -66,9 +67,9 @@ Project page — cache efficiency trend, verdict distribution, top turns:
66
67
 
67
68
  ## Install
68
69
 
69
- ```bash
70
- pipx install tokenol
71
- ```
70
+ pip install tokenol # CLI commands (daily, sessions, projects, ...)
71
+ pip install 'tokenol[serve]' # adds the live dashboard (tokenol serve)
72
+ pip install 'tokenol[serve,persist]' # adds DuckDB-backed history that survives JSONL deletion
72
73
 
73
74
  Requires Python 3.10+. See [tokenol on PyPI](https://pypi.org/project/tokenol/).
74
75
 
@@ -152,6 +153,31 @@ The dashboard updates via SSE as Claude Code writes events to disk. The server g
152
153
 
153
154
  If SSE delivery silently stalls (browser tab throttling, extension hooks, long-lived `EventSource` quirks), the client self-heals: it polls `/api/snapshot` every 30 s as a backstop, force-reconnects on tab-visibility return, and runs a 90 s staleness watchdog. `/api/snapshot` reuses the broadcaster's cached payload while an SSE group is live, so the backstop costs only a JSON serialize. Hover the live-status dot for a "last update Ns ago" indicator.
154
155
 
156
+ ### Persistent history (opt-in)
157
+
158
+ By default, `tokenol serve` parses your `~/.claude*/projects/**/*.jsonl` files into an in-memory model on each restart — fast, but the dashboard loses any session whose JSONL has been deleted or rotated.
159
+
160
+ Pass `--persist` to enable a DuckDB-backed history store at `~/.tokenol/history.duckdb` (override with `TOKENOL_HISTORY_PATH`). The store contains **no message content** — only token counts, costs, models, timestamps, tool counts, and session metadata, comparable to a billing receipt. With persistence on:
161
+
162
+ - **Deleting a JSONL no longer drops it from the dashboard.** Quantitative panels render as before; only the per-turn modal's verbatim content snippets become unavailable, indicated by an "Archived — text snippets unavailable" badge. Metrics survive; words don't (matching the privacy intent of the deletion).
163
+ - **Restart picks up where you left off.** A background flusher batches writes (every 30 s or 100 turns, whichever first) and force-drains on graceful shutdown. The JSONLs remain the durable substrate — a process crash mid-flush loses nothing because the next start re-derives the missing window from the JSONLs (idempotent on `message.id:requestId`).
164
+ - **Cold start stays bounded.** The hot tier loads only the last `hot_window_days` of turns (default 90, tunable via `/api/prefs`); older history is read on demand from the warm tier.
165
+
166
+ Measured cost on the author's full `~/.claude*` corpus (~1820 files, ~2 GB of JSONLs, page cache cold both runs):
167
+
168
+ | | Default mode | `--persist` first start | `--persist` subsequent starts |
169
+ |---|---|---|---|
170
+ | Time to first paint | ~5 s | ~12 s | ~12 s |
171
+ | Wall to settle | ~5 s | ~4 min (one-time backfill) | <30 s |
172
+ | Steady RSS | ~250 MiB | — | ~750 MiB |
173
+ | Durable disk | 0 | ~40 MB after backfill | grows incrementally |
174
+
175
+ Requires the persist extras (`pip install 'tokenol[serve,persist]'`).
176
+
177
+ See `docs/superpowers/specs/2026-05-03-opt-in-persistence-design.md` for design rationale and `docs/superpowers/specs/2026-05-02-persistent-history-design.md` for the underlying store design.
178
+
179
+ ### Main dashboard
180
+
155
181
  Main page layout (top to bottom):
156
182
 
157
183
  | Panel | What it shows |
@@ -33,9 +33,9 @@ Project page — cache efficiency trend, verdict distribution, top turns:
33
33
 
34
34
  ## Install
35
35
 
36
- ```bash
37
- pipx install tokenol
38
- ```
36
+ pip install tokenol # CLI commands (daily, sessions, projects, ...)
37
+ pip install 'tokenol[serve]' # adds the live dashboard (tokenol serve)
38
+ pip install 'tokenol[serve,persist]' # adds DuckDB-backed history that survives JSONL deletion
39
39
 
40
40
  Requires Python 3.10+. See [tokenol on PyPI](https://pypi.org/project/tokenol/).
41
41
 
@@ -119,6 +119,31 @@ The dashboard updates via SSE as Claude Code writes events to disk. The server g
119
119
 
120
120
  If SSE delivery silently stalls (browser tab throttling, extension hooks, long-lived `EventSource` quirks), the client self-heals: it polls `/api/snapshot` every 30 s as a backstop, force-reconnects on tab-visibility return, and runs a 90 s staleness watchdog. `/api/snapshot` reuses the broadcaster's cached payload while an SSE group is live, so the backstop costs only a JSON serialize. Hover the live-status dot for a "last update Ns ago" indicator.
121
121
 
122
+ ### Persistent history (opt-in)
123
+
124
+ By default, `tokenol serve` parses your `~/.claude*/projects/**/*.jsonl` files into an in-memory model on each restart — fast, but the dashboard loses any session whose JSONL has been deleted or rotated.
125
+
126
+ Pass `--persist` to enable a DuckDB-backed history store at `~/.tokenol/history.duckdb` (override with `TOKENOL_HISTORY_PATH`). The store contains **no message content** — only token counts, costs, models, timestamps, tool counts, and session metadata, comparable to a billing receipt. With persistence on:
127
+
128
+ - **Deleting a JSONL no longer drops it from the dashboard.** Quantitative panels render as before; only the per-turn modal's verbatim content snippets become unavailable, indicated by an "Archived — text snippets unavailable" badge. Metrics survive; words don't (matching the privacy intent of the deletion).
129
+ - **Restart picks up where you left off.** A background flusher batches writes (every 30 s or 100 turns, whichever first) and force-drains on graceful shutdown. The JSONLs remain the durable substrate — a process crash mid-flush loses nothing because the next start re-derives the missing window from the JSONLs (idempotent on `message.id:requestId`).
130
+ - **Cold start stays bounded.** The hot tier loads only the last `hot_window_days` of turns (default 90, tunable via `/api/prefs`); older history is read on demand from the warm tier.
131
+
132
+ Measured cost on the author's full `~/.claude*` corpus (~1820 files, ~2 GB of JSONLs, page cache cold both runs):
133
+
134
+ | | Default mode | `--persist` first start | `--persist` subsequent starts |
135
+ |---|---|---|---|
136
+ | Time to first paint | ~5 s | ~12 s | ~12 s |
137
+ | Wall to settle | ~5 s | ~4 min (one-time backfill) | <30 s |
138
+ | Steady RSS | ~250 MiB | — | ~750 MiB |
139
+ | Durable disk | 0 | ~40 MB after backfill | grows incrementally |
140
+
141
+ Requires the persist extras (`pip install 'tokenol[serve,persist]'`).
142
+
143
+ See `docs/superpowers/specs/2026-05-03-opt-in-persistence-design.md` for design rationale and `docs/superpowers/specs/2026-05-02-persistent-history-design.md` for the underlying store design.
144
+
145
+ ### Main dashboard
146
+
122
147
  Main page layout (top to bottom):
123
148
 
124
149
  | Panel | What it shows |