Skip to content

Commit b8e737a

Browse files
committed
refactor(web): rebrand the web output as the "OpenTab browser", not a "report"
OpenTab is a browser for your AI spend, not a report generator -- so the --html/--serve/--web output is the "OpenTab browser" everywhere users read it: the page title/brand/footer, the CLI help for --html/--serve/--web/--theme/ --bind, the console lines, the TUI theme picker, and the README/CLAUDE docs. The product name is "OpenTab" (never "opentab"/"Opentab") in prose; the command, package, import, theme id, config paths, and the default opentab-report.html filename stay lowercase/unchanged, and internal symbols (ReportServer, open_report) are untouched. No behaviour change.
1 parent e1ad0b7 commit b8e737a

11 files changed

Lines changed: 56 additions & 56 deletions

File tree

CLAUDE.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ ruff format --check src/opentab test_opentab.py # format check (matches CI)
4747
python3 -m compileall -q src/opentab # byte-compile smoke check
4848
python3 -m opentab --demo # run the TUI with anonymized/synthetic data
4949
opentab --status "$PWD" # one-shot: current session's cost incl. subagents (tmux status line; OpenCode only)
50-
opentab --demo --html demo.html # one-shot: write the self-contained HTML report
51-
opentab --serve # same report served on http://localhost:8321 (+ live Turns/Tools)
52-
opentab --web # --serve, and open the report in the default browser
50+
opentab --demo --html demo.html # one-shot: write the self-contained HTML browser
51+
opentab --serve # same browser served on http://localhost:8321 (+ live Turns/Tools)
52+
opentab --web # --serve, and open it in the default browser
5353
```
5454

5555
`test_opentab.py` is **not** pytest — it has its own runner at the bottom that just runs
@@ -97,7 +97,7 @@ Follow [Conventional Commits](https://www.conventionalcommits.org) — `type(sco
9797
`--refresh-models` / `r` in the `P` overlay), the warm-start rollup cache under
9898
`~/.config/opentab/cache/` (one JSON per backend, rewritten after a parse when that
9999
backend's files change; off under `--demo` / `--no-cache`), `opentab-*.csv` exports
100-
(on `e`), and the HTML report (only on `--html`, default `opentab-report.html`).
100+
(on `e`), and the HTML browser (only on `--html`, default `opentab-report.html`).
101101
- **Python 3.9+.** `MIN_PYTHON = (3, 9)`; `target-version = py39`. Don't use newer syntax.
102102

103103
## Architecture
@@ -117,11 +117,11 @@ src/opentab/
117117
util.py clipboard/launchers/git_root/fuzzy/parse_range/tool_namespace
118118
sources.py make_store/resolve_source/available_sources/source_cycle + path routing
119119
state.py load_state/save_state/apply_state
120-
themes.py THEMES palettes (single source for the web report + the TUI) + hex math
120+
themes.py THEMES palettes (single source for the web browser + the TUI) + hex math
121121
stores/ opencode, claude, codex, hermes, csv_source, jsonl_source, copilot, vscode, pi, openclaw, combined, cached
122122
tui/ renderer (Renderer), app (App)
123123
web.py build_payload/session_extras + html_command/serve_command (ReportServer)
124-
webpage.py render_html: the self-contained report page (inline CSS/JS strings)
124+
webpage.py render_html: the self-contained browser page (inline CSS/JS strings)
125125
```
126126

127127
Three logical layers (the class names below live in the files above — `Store` in
@@ -434,7 +434,7 @@ exists. The `c`/`L`/price-prompt pickers are all small centered modals via
434434
`Renderer.draw_modal` (drawn after the body so context shows behind), unlike the full-body
435435
help/prices/trends overlays.
436436

437-
### The web report (`--html` / `--serve`)
437+
### The web browser (`--html` / `--serve`)
438438

439439
A second frontend over the same data, stdlib-only (`http.server`), curses-free (works on
440440
native Windows). `cli.web_command` builds the usual **headless App** — rollups, worktree
@@ -504,8 +504,8 @@ export **omits Turns/Tools** (embedding them would be a startup-wide scan), whil
504504
`--serve` (`web.ReportServer`) exposes them as `/api/session/<id>` fetched on drill-in,
505505
plus `/api/reload`. The server is **deliberately single-threaded** — the stores' sqlite
506506
connections are bound to their creating thread — and binds 127.0.0.1 by default (the
507-
report leaks prompt titles/paths/spend; `--bind` warns beyond localhost). **`--web` is
508-
`--serve` plus opening the report in the default browser** (`web.open_report`, stdlib
507+
browser leaks prompt titles/paths/spend; `--bind` warns beyond localhost). **`--web` is
508+
`--serve` plus opening it in the default browser** (`web.open_report`, stdlib
509509
`webbrowser``open`/`xdg-open`/Windows shell-association, so it's cross-platform),
510510
launched from a daemon thread once the socket is listening (a console-browser fallback
511511
can't block `serve_forever`, and it never touches the sqlite-bound store); a headless box

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
</p>
1111

1212
<p align="center">
13-
<img src="https://github.com/user-attachments/assets/a84029a3-7887-42c7-ba5a-d8c905f3fa19" alt="OpenTab web report — the same data as a self-contained page" width="900">
14-
<br><sub><b>Also a web report</b> — <code>opentab --web</code> renders the same data as one self-contained, shareable page</sub>
13+
<img src="https://github.com/user-attachments/assets/a84029a3-7887-42c7-ba5a-d8c905f3fa19" alt="OpenTab web browser — the same data as a self-contained page" width="900">
14+
<br><sub><b>Also a web browser</b> — <code>opentab --web</code> renders the same data as one self-contained, shareable page</sub>
1515
</p>
1616

1717
A local, standard-library terminal UI for your AI coding spend. It reads the records your
@@ -39,13 +39,13 @@ no accounts — it opens those files **read-only**. Standard-library-only at run
3939
- Git worktrees folded into their main repo
4040
- Live fuzzy filter (fzf-style, title / project / id) and live date-range scoping
4141
- CSV export of any view
42-
- A self-contained **HTML report** (`--html`) — same drill-in, calendar heatmap, and
42+
- A self-contained **HTML browser** (`--html`) — same drill-in, calendar heatmap, and
4343
`$` toggle in one shareable file — and a local live server for it (`--serve`, or
4444
`--web` to also pop it open in your browser)
4545
- Keyboard- and mouse-driven (scroll, click to select, double-click to drill)
4646
- Remembers your range, sort, ignored projects, and the `$` view between runs
4747
- Colour themes (opentab, Catppuccin, Tokyo Night, Gruvbox, Nord, Dracula, Rosé Pine —
48-
light and dark) shared by the TUI (`C`) and the web report, set with `--theme`
48+
light and dark) shared by the TUI (`C`) and the web browser, set with `--theme`
4949
- Read-only, local-only, standard-library runtime (nothing extra to pull in)
5050
- Demo mode for screenshots and live demos
5151

@@ -87,7 +87,7 @@ touches, all on your own machine:
8787
source, range, and sort; disable with `--no-state`), an optional model-price cache at
8888
`~/.config/opentab/prices.json` (only when you run `--refresh-models` or press `r` in the
8989
`P` overlay), and — only when you press `e` or run `--html` — an `opentab-*.csv`
90-
export or the HTML report file in the current directory.
90+
export or the HTML browser file in the current directory.
9191
- **Runs** external programs only on the key you press: your file opener
9292
(`open`/`xdg-open`, or Explorer on Windows) for `o`, and for `L` either `tmux`, your own
9393
[launcher hook](#custom-launchers) (`~/.config/opentab/launcher`), or your clipboard tool
@@ -155,7 +155,7 @@ opentab --db /path/to/opencode.db # default: ~/.local/share/opencode/opencode.d
155155
opentab --source claude # browse Claude Code spend instead (see below)
156156
opentab --demo # safe for live demos / screenshots (see below)
157157
opentab --html # write opentab-report.html and exit (see below)
158-
opentab --serve # same report on http://localhost:8321, live
158+
opentab --serve # same browser on http://localhost:8321, live
159159
opentab --web # --serve, and open it in your browser
160160
```
161161

@@ -235,7 +235,7 @@ and months, and the model mix (which models, in what ratio) — but the absolute
235235
numbers do not. A `DEMO — synthetic` tag shows in the header so synthetic figures
236236
are never mistaken for real ones.
237237

238-
### Web report (`--html` / `--serve` / `--web`)
238+
### Web browser (`--html` / `--serve` / `--web`)
239239

240240
`opentab --html` writes the whole browser as **one self-contained HTML file**
241241
no server, no dependencies, works from disk or any static host. It's the TUI in
@@ -255,12 +255,12 @@ this year, or a custom span). The **theme** button switches the palette live
255255
Pine — light and dark) and remembers your pick; `--theme <name>` bakes the
256256
default. Combine with `--demo` for a page you can publish.
257257

258-
`opentab --serve` serves the same report on `http://localhost:8321` (`--port`)
258+
`opentab --serve` serves the same browser on `http://localhost:8321` (`--port`)
259259
and adds what a static file can't have: the per-session **Turns** timeline and
260260
**Tools** attribution fetched live on drill-in, plus a refresh button that
261-
re-reads your data. `opentab --web` is the same thing but also opens the report in
262-
your default browser (cross-platform — `open` on macOS, `xdg-open` on Linux, the
263-
shell association on Windows). It binds to localhost only — the report shows prompt
261+
re-reads your data. `opentab --web` is the same thing but also opens it in
262+
your default web browser (cross-platform — `open` on macOS, `xdg-open` on Linux, the
263+
shell association on Windows). It binds to localhost only — the browser shows prompt
264264
titles, project paths, and spend, so if you want it on another machine put it behind
265265
something like Tailscale (`--bind`), never a public interface.
266266

@@ -295,7 +295,7 @@ detail — cost split, model mix, and subagent tree. `Esc` steps back out.
295295
| `o` | Open the selected session's / project's directory |
296296
| `L` | Launch the selected session in its own tool (`opencode --session <id>` / `claude --resume <id>` / `codex resume <id>`). Inside tmux a one-key menu opens it in a new **w**indow, **s**plit, **v**split, or **p**opup (cd'd to the project), with **y** copying the `cd <project> && …` command to the clipboard; outside tmux the menu offers only the copy. See [Custom launchers](#custom-launchers) to route launches through your own tooling |
297297
| `c` | Switch data source — any present backend (OpenCode, Claude Code, Codex, Hermes, Copilot CLI, VS Code, pi, OpenClaw, CSV, JSONL), or all merged |
298-
| `C` | Colours — pick a theme (opentab, Catppuccin Mocha/Latte, Tokyo Night/Day, Gruvbox, Nord, Dracula, Rosé Pine — the same set as the web report). `j`/`k` live-preview, `Enter` keeps, `Esc` reverts; light themes light up the whole screen. `--theme` sets it at launch |
298+
| `C` | Colours — pick a theme (opentab, Catppuccin Mocha/Latte, Tokyo Night/Day, Gruvbox, Nord, Dracula, Rosé Pine — the same set as the web browser). `j`/`k` live-preview, `Enter` keeps, `Esc` reverts; light themes light up the whole screen. `--theme` sets it at launch |
299299
| `r` | Reload the database |
300300
| `?` | Help; `q` quits |
301301

src/opentab/cli.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,32 +170,32 @@ def parse_args() -> argparse.Namespace:
170170
const="opentab-report.html",
171171
default=None,
172172
metavar="FILE",
173-
help="write a self-contained HTML report and exit: drill-in by month/day/"
173+
help="write a self-contained HTML browser and exit: drill-in by month/day/"
174174
"project/session, calendar heat map, sortable tables, the $ what-if toggle "
175175
"-- all client-side in one file (default FILE: opentab-report.html). "
176176
"Pairs with --demo for a shareable page",
177177
)
178178
parser.add_argument(
179179
"--serve",
180180
action="store_true",
181-
help="serve the HTML report from a local web server; adds the per-session "
181+
help="serve the HTML browser from a local web server; adds the per-session "
182182
"Turns/Tools drill-in as live endpoints and a data-refresh button "
183183
"(Ctrl-C stops it)",
184184
)
185185
parser.add_argument(
186186
"--web",
187187
action="store_true",
188-
help="like --serve, but also open the report in your default browser "
188+
help="like --serve, but also open it in your default web browser "
189189
"(cross-platform via the stdlib webbrowser: `open` on macOS, `xdg-open` on "
190190
"Linux, the shell association on Windows); honors --port/--bind",
191191
)
192192
parser.add_argument(
193193
"--theme",
194194
choices=themes.THEME_IDS,
195195
default=themes.DEFAULT_THEME,
196-
help="colour theme for the TUI and the --html/--serve report (opentab, "
196+
help="colour theme for the TUI and the --html/--serve browser (opentab, "
197197
"catppuccin-mocha/latte, tokyo-night/-day, gruvbox, nord, dracula, rose-pine); "
198-
"switch live in the TUI with Y or the report's theme button, and your choice is "
198+
"switch live in the TUI with Y or the browser's theme button, and your choice is "
199199
f"remembered. Default: {themes.DEFAULT_THEME}",
200200
)
201201
parser.add_argument(
@@ -204,7 +204,7 @@ def parse_args() -> argparse.Namespace:
204204
parser.add_argument(
205205
"--bind",
206206
default="127.0.0.1",
207-
help="address for --serve/--web (default: 127.0.0.1). The report exposes prompt "
207+
help="address for --serve/--web (default: 127.0.0.1). The browser exposes prompt "
208208
"titles, project paths, and spend -- bind beyond localhost only on a "
209209
"trusted/VPN (e.g. Tailscale) interface, never a public one",
210210
)
@@ -446,7 +446,7 @@ def main() -> int:
446446
or getattr(args, "serve", False)
447447
or getattr(args, "web", False)
448448
):
449-
return web_command(args) # HTML report / local report server; no curses
449+
return web_command(args) # HTML browser / local browser server; no curses
450450
if curses is None:
451451
raise SystemExit(
452452
"OpenTab needs Python's curses module, which native Windows Python doesn't bundle.\n"

src/opentab/state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def save_state(app: App) -> None:
4343
"bookmarks": sorted(app.bookmarks), # sessions starred with `b`
4444
"show_api_prices": app.show_api_prices,
4545
"source": app.source_key, # restore the last source (opencode/claude/all) next run
46-
"theme": app.theme_id, # the colour theme (shared with the web report)
46+
"theme": app.theme_id, # the colour theme (shared with the web browser)
4747
"cal_levels": app.cal_levels, # the Calendar heat-map granularity (+/-)
4848
"prices_prompt_dismissed": app.prices_prompt_dismissed, # "don't ask again" for prices
4949
}

src/opentab/stores/opencode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def cache_inputs(self) -> list[str]:
190190
# The DB file whose (size, mtime) fingerprints the warm-start cache, plus its
191191
# WAL sidecars. OpenCode runs SQLite in WAL mode, so new sessions land in
192192
# <db>-wal and the main .db's size/mtime don't move until a checkpoint -- so
193-
# fingerprinting the .db alone made a reload (r, or the report's refresh) serve
193+
# fingerprinting the .db alone made a reload (r, or the browser's refresh) serve
194194
# the stale cache and never show sessions written since. The sidecars move on
195195
# every commit; a read-only connection still reads them, so a re-parse sees the
196196
# new rows. Missing sidecars (a non-WAL DB, or a checkpoint that removed them)

src/opentab/themes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
"""Shared theme palettes — the single source of truth for the web report and the TUI.
1+
"""Shared theme palettes — the single source of truth for the web browser and the TUI.
22
33
Each theme names a role palette (semantic slots, not hues) plus a calendar heat ramp
44
(`heat`, coldest→hottest) and a cheap→pricey price-heat ramp (`price_heat`), and a
5-
`dark` flag. The web report reads these verbatim (`web_payload()` reshapes them to the
5+
`dark` flag. The web browser reads these verbatim (`web_payload()` reshapes them to the
66
JS token names and is injected into the page), and the curses TUI maps the same role
77
hexes to color pairs via `init_color` on true-color terminals (or nearest-256 elsewhere).
88
Adding a theme is one entry here; both frontends and the `--theme` CLI choices pick it up.

src/opentab/tui/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def __init__(self, store: Store, args: argparse.Namespace, source_key: str = "")
200200
self.source_menu_index = 0 # highlighted row in that picker
201201
self.sort_menu = False # the `s` sort-order picker overlay
202202
self.sort_menu_index = 0 # highlighted row in that picker
203-
# Active colour theme (shared source with the web report). Seeded from
203+
# Active colour theme (shared source with the web browser). Seeded from
204204
# --theme; a valid saved theme (apply_state) or the `Y` picker takes over.
205205
self.theme_id = getattr(args, "theme", None) or themes.DEFAULT_THEME
206206
if self.theme_id not in themes.THEMES:
@@ -2607,7 +2607,7 @@ def run(self, stdscr: curses.window) -> None:
26072607
curses.curs_set(0)
26082608
curses.use_default_colors()
26092609
# Colours come from the active theme (opentab/themes.py, shared with the web
2610-
# report). The renderer maps its role hexes onto the fixed color-pair layout --
2610+
# browser). The renderer maps its role hexes onto the fixed color-pair layout --
26112611
# exact via init_color on true-colour terminals, nearest-256 otherwise, and the
26122612
# generated ANSI ramp on 8-colour. Foreground-only: a TUI paints over the
26132613
# terminal's own background. Live theme switches re-run init_theme_colors().

src/opentab/tui/renderer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,7 +2077,7 @@ def help_sections(self) -> list[tuple[str, list[tuple]]]:
20772077
(
20782078
"C",
20792079
"colour-theme picker — j/k live-preview · Enter keep · Esc revert "
2080-
"(also the web report's)",
2080+
"(also the web browser's)",
20812081
),
20822082
("D", "toggle real / demo data (demo anonymizes titles and paths)"),
20832083
],
@@ -2610,11 +2610,11 @@ def draw_source_menu(self, stdscr: curses.window, scr_h: int, scr_w: int) -> Non
26102610

26112611
def draw_theme_menu(self, stdscr: curses.window, scr_h: int, scr_w: int) -> None:
26122612
# The `C` (Colours) picker: a modal list of the themes (shared with the web
2613-
# report). j/k live-previews each (the whole UI is the swatch), Enter keeps it,
2613+
# browser). j/k live-previews each (the whole UI is the swatch), Enter keeps it,
26142614
# Esc reverts to the theme active on open. Colours re-map via init_theme_colors.
26152615
entries = self.theme_menu_entries()
26162616
idx = self.theme_menu_index % len(entries) if entries else 0
2617-
lines = [("Colour theme (also the web report's):", curses.color_pair(4)), ("", 0)]
2617+
lines = [("Colour theme (also the web browser's):", curses.color_pair(4)), ("", 0)]
26182618
for offset, (_tid, name, is_current) in enumerate(entries):
26192619
marker = "●" if is_current else "○"
26202620
suffix = " (current)" if is_current else ""

0 commit comments

Comments
 (0)