cctally 1.58.0 → 1.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/bin/_cctally_dashboard.py +47 -2
- package/bin/_cctally_tui.py +34 -1
- package/bin/_lib_view_models.py +10 -0
- package/dashboard/static/assets/index-DQH6UPc_.js +80 -0
- package/dashboard/static/assets/index-uGwZSssU.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +1 -1
- package/dashboard/static/assets/index-CXyhwVOb.js +0 -80
- package/dashboard/static/assets/index-Ccgg8U8_.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.60.0] - 2026-07-03
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Dashboard **Recent Sessions** is denser and more useful. When every recent session uses the same model, the redundant per-row model column is dropped entirely — it previously rendered as a column of dots under a still-labelled "Model" header, which read as broken — and a single "all · model" caption in the header says it instead; a mixed set of models still shows the column. Two columns that carry real signal take its place: a **Session** title for each row (its first prompt, or an AI-generated summary when one exists) and a **Cache** hit-rate. The title is private conversation content, so it appears only when transcript viewing is enabled for how you reached the dashboard (on your own machine, or on the LAN only if you have explicitly exposed transcripts) and shows a dash otherwise. All the columns now fit at a common laptop width without sideways scrolling, and a long title shortens with the full text available on hover (#264).
|
|
12
|
+
- Dashboard **$/1% Trend** detail view reads better and gains a cost column. When there is too little history to compute a four-week median, the two formerly-empty "—" median tiles collapse into a single "needs 4 weeks" hint instead of looking broken. The weekly table gains a sortable **Cost** column (what you spent that week) and the whole view opens in a wider two-pane layout — the chart and its headline numbers on the left, the sortable weekly table on the right — so it fills the space instead of stacking tall and scrolling; sorting the table reorders only the pop-up, not the small card behind it (#264).
|
|
13
|
+
- The dashboard's usage history is split back into three independent **Daily**, **Weekly**, and **Monthly** cards, each opening its own detail view — undoing an earlier consolidation that folded them into a single "History" card and hid Weekly and Monthly behind an easy-to-miss Day/Week/Month toggle. Every card now has a discoverable header and a keyboard shortcut, and the Weekly and Monthly views open in a wider two-pane layout — the period navigator across the top, the selected period's model-and-token breakdown on the left, and a sortable table of every period on the right — so the full history fits without scrolling. The daily heatmap still deep-links a clicked day straight into the Daily view (#264).
|
|
14
|
+
- Dashboard board and hero redesigned to fix the wasted space the previous facelift left behind. The board is now a content-aware **bento grid** — cards sit in height-matched rows sized to their content and the layout goes full-width (fluid up to 2100px) instead of capping at 1600px — so a wide screen fills with data instead of stranding a tall column of half-empty full-width cards, roughly halving the page height, while still collapsing to a single column on mobile and keeping drag-to-reorder within each row. The hero is rebuilt into three zones so the two questions you actually ask each get a dominant number: weekly usage % (with 5-hour usage paired beside it and the reset countdown) and dollars spent this week (with $/1% as its sub-line), alongside a compact support column for the end-of-week forecast, the week-over-week $/1% trend, and a snapshot-freshness reading that now stays calm for a normal few-minute-old snapshot and only turns amber, then red, once the data is genuinely stale. Every card also gains a consistent ⤢ expand control in its header to open its detail view (previously some cards signalled "open" only via the share icon, and the Blocks card had no click-to-open at all), the Forecast card gains a pace bar toward its 100% cap, and the empty Alerts card shows a teaching gauge of your current usage against the configured alert thresholds (#264).
|
|
15
|
+
- Dashboard desktop board fixes that finish the bento redesign. Cards whose content is taller than their row now scroll inside their own frame instead of overflowing and rendering on top of the card below them — previously the $/1% Trend chart could spill over the Daily card beneath it, and the Weekly and Monthly cards did the same. The 5-hour **Blocks** card now shows every block for the week (scroll inside the card to reach them all) instead of only the three most recent with no way to see the rest. The **Daily** heatmap shows each day's dollar amount again on the card, not just in its detail view. And the per-card collapse arrow — which did nothing on desktop once the board became height-matched — is removed there (the ⤢ expand control opens each card's detail); it still works on smaller stacked screens (#264).
|
|
16
|
+
- Dashboard mobile fixes. The **Recent Sessions** panel title is no longer crushed to nothing on a phone; each session in the list now leads with its title (what the session was about) instead of hiding it; the at-a-glance hero is a compact card rather than three tall stacked zones; and the Daily card title no longer clips (#264).
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Dashboard: the empty **Recent Alerts** card no longer sits bottom-heavy with a band of empty space above it — it now shows the same "you're clear" gauge as the alerts detail view (your current usage against the configured alert thresholds), centered and sized to the card, so it reads as balanced as its Forecast and Blocks neighbours (#265).
|
|
20
|
+
- Dashboard: the **$/1% Trend** card keeps its sparkline and "older ▸ newer" legend pinned in view and scrolls the weekly table beneath them, so with only a few weeks of history the chart is no longer half-hidden below the card's internal scroll fold (#265).
|
|
21
|
+
- Dashboard: the **Blocks** card's expand (⤢) control is now disabled on a week with no activity blocks yet, instead of looking clickable but opening nothing when pressed (#265).
|
|
22
|
+
- Dashboard: the **Weekly** and **Monthly** cards now list every week and month and scroll within the card, instead of showing only the three most recent behind a scrollbar that revealed nothing more — so the whole history is reachable without opening the detail view (#265).
|
|
23
|
+
- Dashboard: the **Daily** heatmap no longer clips the top off each day-of-month number — the card row is a little taller so every day's number and its dollar amount are fully legible (#265).
|
|
24
|
+
|
|
25
|
+
## [1.59.0] - 2026-07-02
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Dashboard detail modals now scale their charts and tables to the real data instead of collapsing on it. The Cache Report hit-% timeline auto-zooms its y-axis to the data band, so a line clustered at 96–98% shows real day-to-day variation and the ±5pp median band is visible instead of pinning flat to the top of a fixed 0–100% axis. The Projects chart switches from an unreadable near-solid stacked area to a ranked top-N + "(other)" horizontal-bar view when one project dominates the window (each bar still drills into that project), and the stacked-area mode gains y-axis labels. The Forecast range bar gets brighter, outlined zones, a 0→110% scale, a "now ▸" marker at the current weekly usage, and a legend. The Trend chart's secondary "used %" line gains its own right-hand axis, and the whole chart becomes interactive with hover- and keyboard-focus tooltips (week · $/1% · used %). A single-model session collapses its redundant "Models" and "Cost by model" sections into one "Model · … · $…" line, and the empty Recent Alerts state becomes a teaching gauge showing current usage against the configured fire thresholds. On mobile, the Cache Report daily table reflows into labeled cards instead of unlabeled number run-ons and its header no longer truncates to "Cache ⋯". Every panel modal now lands keyboard and screen-reader focus on its heading when it opens (#250).
|
|
29
|
+
- Dashboard dates and labels now read consistently and say what they mean. Every human-facing date and timestamp renders in one style — the Session modal's Started / Last activity and the header freshness tooltip localize to your display timezone ("Jun 28 12:08 +03") instead of leaking a raw UTC ISO stamp, and every Cache Report date (daily table and mobile cards, today's spotlight, and the Net-$ chart axis) reads "Jun 29" instead of three competing "2026-06-29" / "06-29" forms. The Trend view states a single, self-consistent week count derived from the data instead of contradicting itself with a hardcoded "12" / "8", and its chart median line now names its window ("10-wk median $26.93") so it is no longer confused with the hero's separate "4-week median". The Projects table renames its two percent columns to state what they actually measure — "Used pp" (percentage-points summed across the window, so a 165 no longer reads as a broken percentage) and "Cost share" — each with a hover tooltip spelling out the definition. The active 5-hour Block projection now reads "191 min left" instead of "191M LEFT" (which looked like millions under the uppercase label), and the Cache Report by-project list shows plain repo basenames ("cctally-dev") with the full path on hover (#251).
|
|
30
|
+
- Dashboard Settings overlay redesigned into one predictable deferred-commit form: every edit is now staged with a "Save · N changes" badge (Save is disabled until you actually change something) and per-section change markers, and the Reset buttons no longer silently discard your other pending edits — closing a long-standing data-loss trap where a reset applied instantly and closed the sheet, dropping any unsaved timezone/alerts/sort/filter edits. Disabled rows (the Codex budget toggles when no Codex budget is set) now read as dimmed, Alerts are grouped into Threshold / Budget / Test with the projected-weekly toggle nested under its master, the three scattered Reset buttons are consolidated into one "Restore defaults" section with explicit non-overlapping scopes, the Save/Cancel bar is now sticky on desktop as well as mobile, and an accidental Esc or backdrop click while you have unsaved changes now asks before discarding (#252).
|
|
31
|
+
- Dashboard chrome and controls are now more discoverable, legible, and accessible. The Settings (⚙) and Help (?) icons are shown in the desktop header instead of being reachable only via keyboard shortcuts, and the sync-freshness indicator reads in plain language ("synced 8m ago" rather than "synced 480s ago") and escalates colour as it ages — neutral when fresh, amber past 5 minutes, red past 30 — with a matching freshness dot surfaced on mobile where the text is hidden. The Doctor chip reconciles to the freshest report when you open its modal (so it no longer under-reports a failure the modal is already showing), and the Doctor category headers gain an expand/collapse caret. In Recent Sessions, search now highlights the matched text in-cell and gives the current match a distinct emphasis with `aria-current` instead of one uniform wash across every match, and on mobile the search box drops to its own full-width row so a remembered-filter chip can no longer crush it to a sliver. Sortable table headers show a dim glyph at rest so they look sortable before you click, the Sessions control buttons gain the standard 2px accent focus ring, the Doctor and Basket chips now meet the 44px mobile touch target, the Weekly/Monthly/Projects modal rows are keyboard-selectable (Tab, then Enter/Space), and the Help overlay's key/description columns are tightened for readability (#253).
|
|
32
|
+
- Dashboard's Daily, Weekly, and Monthly views are now one **History** modal with a Day / Week / Month toggle, opened from a single "History" card (the daily-usage heatmap) and a single keyboard shortcut instead of three separate cards and shortcuts. All three periods share one mini-bar navigator — Weekly and Monthly gain the visual navigator the Daily view already had — and the per-period detail adopts the Projects drill's cleaner presentation (per-model cost bars and a keyboard-hint footer). The up/down arrows and the navigator's ‹/› buttons step through the period timeline, the Weekly/Monthly table is now sortable by any column with its previously unlabeled "Δ" column now reading "Δ cost", and the Daily navigator's "today" axis label no longer duplicates the last date. Your last-used period is remembered between opens, and clicking a day in the heatmap still opens that exact day (#254).
|
|
33
|
+
- Dashboard freshness readouts now all speak the same plain language: the Current Week hero's "as of" pill and the Doctor report's "Latest snapshot" line read "1d 3h ago" or "27m ago" instead of a raw seconds count like "97928s ago", finishing the humanization started on the sync-freshness chip in #253 — and any freshness older than a day now reads in days-and-hours ("1d 3h ago") rather than a large hour count (#259).
|
|
34
|
+
- Dashboard Session and 5-hour Block detail modals now present their per-model cost breakdown in the same one-row-per-model format the History cards use — a colour-coded model chip, a bar sized to that model's cost relative to the top model, and the dollar cost — replacing each modal's older one-off stacked bar and separate legend so the breakdown reads consistently everywhere. The Session modal also drops its now-redundant standalone "Models" chip row, since the cost breakdown already labels every model (#260).
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- Dashboard Settings overlay no longer overwrites a pending edit when a concurrent same-field change arrives from another browser tab or a CLI `cctally config set` while the panel is open — the incoming value is adopted only for fields you have not touched since the last sync, so your in-progress timezone, alerts, notifier, or viewer-toggle edit is kept and the "Save · N changes" badge stays accurate; untouched fields still re-seed live as before (#258).
|
|
38
|
+
- Dashboard Forecast modal: the two end-of-week projection pills no longer overlap on narrow and mobile widths — a hard pixel min-gap now pushes them apart (collapsing to a single "low–high%" range pill only when they genuinely cannot fit), and the mobile scale labels no longer crowd into "100%110%" (#250).
|
|
39
|
+
- Dashboard modal polish follow-ups to #250: on mobile the Projects ranked-bar view (shown when one project dominates the window) now wraps each bar's label to its full project name instead of truncating it, so long names are readable on touch where there is no hover tooltip; the Forecast range bar reflows its projection pills immediately when the modal is resized instead of waiting for the next data refresh; and the empty Recent Alerts teaching gauge now gives interior thresholds a distinct middle tone (amber → orange → red) when three or more alert thresholds are configured, instead of painting every non-lowest tick red (#257).
|
|
40
|
+
- Dashboard History modal polish follow-ups to #254: the Day / Week / Month toggle is now an idiomatic keyboard radio group — Tab lands on the currently-selected period rather than always on "Day", and ←/→ move both the focus and the selection through the three options (with wraparound) while ↑/↓ keep stepping the period timeline — and a long model name in the per-period cost breakdown (e.g. `opus-4-6-fast`) no longer wraps onto a second line, so every cost-bar row keeps the same height (#261).
|
|
41
|
+
- Dashboard cost-by-model chips now use one consistent style everywhere the per-model cost breakdown appears: the History detail card's model chips — previously pill-shaped with white text — now match the compact, hue-tinted, monospace chips already used by the Projects drill, Session, and Block modals, so the shared breakdown reads identically across all four surfaces and consistently with the rest of the dashboard's chips (#262).
|
|
42
|
+
- Dashboard Projects drill's per-model cost chips now show a short, one-line model name (e.g. `opus-4-8`) instead of the full canonical id (e.g. `claude-opus-4-8`), which wrapped to a second line in the drill's narrow chip column and left those rows uneven — finishing the cost-by-model chip parity from #262 so all four per-model cost-bar surfaces (History, Session, Block, and now the Projects drill) render short display names identically (#263).
|
|
43
|
+
|
|
8
44
|
## [1.58.0] - 2026-06-30
|
|
9
45
|
|
|
10
46
|
### Added
|
|
@@ -4500,10 +4500,22 @@ def snapshot_to_envelope(snap: "DataSnapshot", *,
|
|
|
4500
4500
|
monotonic_now: "float | None" = None,
|
|
4501
4501
|
oauth_usage_cfg: "dict | None" = None,
|
|
4502
4502
|
display_tz_pref_override: "str | None" = None,
|
|
4503
|
-
runtime_bind: "str | None" = None
|
|
4503
|
+
runtime_bind: "str | None" = None,
|
|
4504
|
+
transcripts_visible: bool = False) -> dict:
|
|
4504
4505
|
"""Serialize a DataSnapshot into the JSON envelope consumed by the
|
|
4505
4506
|
browser (design spec §2.2).
|
|
4506
4507
|
|
|
4508
|
+
``transcripts_visible`` gates the transcript-derived session ``title``
|
|
4509
|
+
(#264 S3): the key is emitted ONLY when the flag is True AND the row has
|
|
4510
|
+
a title, so False (the default) fails closed for any caller that forgets
|
|
4511
|
+
to pass it — no ``title`` key, no leaked prompt content. The two
|
|
4512
|
+
browser-serving emit sites (``GET /api/data`` + the SSE loop) pass the
|
|
4513
|
+
per-request ``_transcripts_visible_to_request()`` — the SAME predicate
|
|
4514
|
+
that drives ``transcriptsEnabled`` and the per-row "open conversation"
|
|
4515
|
+
button; every other caller (share builders, fixtures, tests) keeps the
|
|
4516
|
+
default. ``cache_hit_pct`` (sessions) and ``cost_usd`` (trend) are plain
|
|
4517
|
+
numbers and are never gated.
|
|
4518
|
+
|
|
4507
4519
|
Pure function — no I/O on the snapshot data path. Reads
|
|
4508
4520
|
``config.json`` once for ``display.tz`` and once for the
|
|
4509
4521
|
``alerts_settings`` mirror (cheap atomic-rename reads); the
|
|
@@ -5101,6 +5113,14 @@ def snapshot_to_envelope(snap: "DataSnapshot", *,
|
|
|
5101
5113
|
"dollar_per_pct": w.dollars_per_percent,
|
|
5102
5114
|
"delta": w.delta_dpp,
|
|
5103
5115
|
"is_current": bool(w.is_current),
|
|
5116
|
+
# #264 S3: additive weekly cost (already on the row via
|
|
5117
|
+
# build_trend_view); rendered by the Trend modal's Cost
|
|
5118
|
+
# column. ``getattr`` (like ``project_key`` /
|
|
5119
|
+
# ``trend_history_median_dpp``) tolerates minimal trend
|
|
5120
|
+
# shapes — SimpleNamespace test rows / older fixtures —
|
|
5121
|
+
# that predate this nullable field. ``None`` when the
|
|
5122
|
+
# week has no cost snapshot.
|
|
5123
|
+
"cost_usd": round(_wc, 4) if (_wc := getattr(w, "weekly_cost_usd", None)) is not None else None,
|
|
5104
5124
|
}
|
|
5105
5125
|
for w in snap.trend
|
|
5106
5126
|
],
|
|
@@ -5112,6 +5132,9 @@ def snapshot_to_envelope(snap: "DataSnapshot", *,
|
|
|
5112
5132
|
"dollar_per_pct": w.dollars_per_percent,
|
|
5113
5133
|
"delta": w.delta_dpp,
|
|
5114
5134
|
"is_current": bool(w.is_current),
|
|
5135
|
+
# #264 S3: additive weekly cost (see weeks[] above; same
|
|
5136
|
+
# getattr tolerance for minimal/older trend shapes).
|
|
5137
|
+
"cost_usd": round(_wc, 4) if (_wc := getattr(w, "weekly_cost_usd", None)) is not None else None,
|
|
5115
5138
|
}
|
|
5116
5139
|
for w in (snap.weekly_history or [])
|
|
5117
5140
|
],
|
|
@@ -5155,6 +5178,20 @@ def snapshot_to_envelope(snap: "DataSnapshot", *,
|
|
|
5155
5178
|
# resolve (the React cell falls back to plain text).
|
|
5156
5179
|
"project_key": getattr(s, "project_key", None),
|
|
5157
5180
|
"cost_usd": round(s.cost_usd, 4) if s.cost_usd is not None else None,
|
|
5181
|
+
# #264 S3: cache-hit % is a plain metric — always serialized
|
|
5182
|
+
# (never gated). ``None`` when the row has no denominator.
|
|
5183
|
+
"cache_hit_pct": round(s.cache_hit_pct, 1) if s.cache_hit_pct is not None else None,
|
|
5184
|
+
# The session title is transcript-derived content: emit the
|
|
5185
|
+
# `title` KEY only when the per-request transcript gate is
|
|
5186
|
+
# open AND a title exists. Omitted (not null) otherwise, so
|
|
5187
|
+
# a gated-off or untitled session renders the client's
|
|
5188
|
+
# em-dash fallback and committed goldens (empty conversation
|
|
5189
|
+
# fixtures -> no title) never carry a `title` key. Default
|
|
5190
|
+
# transcripts_visible=False therefore fails fully closed.
|
|
5191
|
+
**({"title": getattr(s, "title", None)}
|
|
5192
|
+
if (transcripts_visible
|
|
5193
|
+
and getattr(s, "title", None) is not None)
|
|
5194
|
+
else {}),
|
|
5158
5195
|
}
|
|
5159
5196
|
for s in snap.sessions
|
|
5160
5197
|
],
|
|
@@ -7356,6 +7393,10 @@ class DashboardHTTPHandler(BaseHTTPRequestHandler):
|
|
|
7356
7393
|
cfg_oauth = _get_oauth_usage_config(load_config())
|
|
7357
7394
|
except OauthUsageConfigError:
|
|
7358
7395
|
cfg_oauth = dict(sys.modules["cctally"]._OAUTH_USAGE_DEFAULTS)
|
|
7396
|
+
# Resolve the per-request transcript gate ONCE and use it for both the
|
|
7397
|
+
# in-envelope session `title` gate (#264 S3) and the `transcriptsEnabled`
|
|
7398
|
+
# client signal below — one predicate, two consumers, desync impossible.
|
|
7399
|
+
visible = self._transcripts_visible_to_request()
|
|
7359
7400
|
env = snapshot_to_envelope(
|
|
7360
7401
|
snap,
|
|
7361
7402
|
# `_now_utc()` honors CCTALLY_AS_OF for harness determinism;
|
|
@@ -7368,6 +7409,7 @@ class DashboardHTTPHandler(BaseHTTPRequestHandler):
|
|
|
7368
7409
|
oauth_usage_cfg=cfg_oauth,
|
|
7369
7410
|
display_tz_pref_override=type(self).display_tz_pref_override,
|
|
7370
7411
|
runtime_bind=type(self).cctally_host,
|
|
7412
|
+
transcripts_visible=visible,
|
|
7371
7413
|
)
|
|
7372
7414
|
# Conversation viewer (Plan 2, spec §5): inject the client signal
|
|
7373
7415
|
# PER-REQUEST and Host-aware — NOT inside snapshot_to_envelope (the
|
|
@@ -7376,7 +7418,7 @@ class DashboardHTTPHandler(BaseHTTPRequestHandler):
|
|
|
7376
7418
|
# request that the transcript GETs would 403 shows
|
|
7377
7419
|
# transcriptsEnabled=false, never enabled-then-403 (the pass-2 P2
|
|
7378
7420
|
# finding) — one predicate, two consumers, desync impossible.
|
|
7379
|
-
env["transcriptsEnabled"] =
|
|
7421
|
+
env["transcriptsEnabled"] = visible
|
|
7380
7422
|
body = json.dumps(env, ensure_ascii=False).encode("utf-8")
|
|
7381
7423
|
self.send_response(200)
|
|
7382
7424
|
self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
@@ -8295,6 +8337,9 @@ class DashboardHTTPHandler(BaseHTTPRequestHandler):
|
|
|
8295
8337
|
oauth_usage_cfg=cfg_oauth,
|
|
8296
8338
|
display_tz_pref_override=type(self).display_tz_pref_override,
|
|
8297
8339
|
runtime_bind=type(self).cctally_host,
|
|
8340
|
+
# #264 S3: gate the in-envelope session `title` on the same
|
|
8341
|
+
# connection-scoped predicate that drives transcriptsEnabled.
|
|
8342
|
+
transcripts_visible=transcripts_enabled,
|
|
8298
8343
|
)
|
|
8299
8344
|
env["transcriptsEnabled"] = transcripts_enabled
|
|
8300
8345
|
msg = (
|
package/bin/_cctally_tui.py
CHANGED
|
@@ -1609,7 +1609,40 @@ def _tui_build_sessions(
|
|
|
1609
1609
|
view = c.build_sessions_view(
|
|
1610
1610
|
entries, now_utc=now_utc, limit=limit, display_tz=None,
|
|
1611
1611
|
)
|
|
1612
|
-
|
|
1612
|
+
rows = list(view.rows)
|
|
1613
|
+
# Attach human-readable titles for the dashboard/TUI Sessions surface.
|
|
1614
|
+
# Reuses the conversation viewer's title derivation (AI title wins, else
|
|
1615
|
+
# first non-marker human line), which reads the CACHE db
|
|
1616
|
+
# (conversation_messages / conversation_ai_titles). Fail-soft: any cache
|
|
1617
|
+
# failure leaves titles ``None`` -> the client renders an em-dash. One
|
|
1618
|
+
# bounded indexed query (<= `limit` session ids, rides idx_conv_session_ts)
|
|
1619
|
+
# per snapshot build. Titles are stashed unconditionally on this
|
|
1620
|
+
# server-internal row; the privacy gate is applied later, at envelope
|
|
1621
|
+
# serialization (``snapshot_to_envelope(transcripts_visible=...)``).
|
|
1622
|
+
if rows:
|
|
1623
|
+
session_ids = [r.session_id for r in rows if r.session_id]
|
|
1624
|
+
try:
|
|
1625
|
+
conn = c.open_cache_db()
|
|
1626
|
+
except (sqlite3.DatabaseError, OSError):
|
|
1627
|
+
conn = None
|
|
1628
|
+
if conn is not None:
|
|
1629
|
+
try:
|
|
1630
|
+
titles = c._load_sibling(
|
|
1631
|
+
"_lib_conversation_query"
|
|
1632
|
+
)._session_titles_map(conn, session_ids)
|
|
1633
|
+
rows = [
|
|
1634
|
+
dataclasses.replace(r, title=titles.get(r.session_id))
|
|
1635
|
+
if r.session_id in titles else r
|
|
1636
|
+
for r in rows
|
|
1637
|
+
]
|
|
1638
|
+
except (sqlite3.DatabaseError, OSError):
|
|
1639
|
+
pass # fail-soft: leave titles None
|
|
1640
|
+
finally:
|
|
1641
|
+
try:
|
|
1642
|
+
conn.close()
|
|
1643
|
+
except sqlite3.Error:
|
|
1644
|
+
pass
|
|
1645
|
+
return rows
|
|
1613
1646
|
|
|
1614
1647
|
|
|
1615
1648
|
@dataclass
|
package/bin/_lib_view_models.py
CHANGED
|
@@ -264,6 +264,16 @@ class TuiSessionRow:
|
|
|
264
264
|
# without re-reading ``session_files`` and so the share path can
|
|
265
265
|
# privacy-scrub via ``_lib_share._scrub``.
|
|
266
266
|
project_path: str | None = None
|
|
267
|
+
# Dashboard/TUI-only: the human-readable session title (AI-generated
|
|
268
|
+
# title when present, else the first non-marker user prompt). Populated
|
|
269
|
+
# by the dashboard/TUI snapshot wrapper (``_cctally_tui._tui_build_sessions``)
|
|
270
|
+
# via ``_lib_conversation_query._session_titles_map``; left ``None`` on the
|
|
271
|
+
# CLI ``session`` / share paths (they call ``build_sessions_view`` directly).
|
|
272
|
+
# This is transcript-derived content — it rides the per-request transcript
|
|
273
|
+
# privacy gate at envelope serialization (never emitted when the gate is
|
|
274
|
+
# closed). Defaults ``None`` so fixture rows built positionally stay
|
|
275
|
+
# compatible.
|
|
276
|
+
title: str | None = None
|
|
267
277
|
|
|
268
278
|
|
|
269
279
|
# === Internal helpers ======================================================
|