cctally 1.56.0 → 1.56.2
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 +14 -0
- package/bin/_lib_conversation_query.py +44 -8
- package/dashboard/static/assets/index-CtgGA2MX.js +78 -0
- package/dashboard/static/assets/{index-C7ow8P26.css → index-DBy0SKfB.css} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +1 -1
- package/dashboard/static/assets/index-D5t2jPCd.js +0 -78
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.56.2] - 2026-06-27
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Conversation viewer: the browse-rail model chip is no longer truncated mid-glyph by the adjacent cost/message-count — it previously clipped "opus" to "opu"/"op" when the fixed-width rail ran short of room; the rail now shows a single rigid primary-model chip plus a "+N" counter for any additional models, and the project/branch text ellipsizes first so the chip and the cost/message numbers always render in full (#243).
|
|
12
|
+
- Conversation viewer: a conversation whose main session ran one model but spawned subagents on another (e.g. opus driving haiku code-explorers) now shows the main-session model as the primary one — both the rail chip and the reader header now list the main model first ("opus, haiku") instead of alphabetically ("haiku, opus"), which previously made a haiku subagent look like the conversation's main model (#243).
|
|
13
|
+
|
|
14
|
+
## [1.56.1] - 2026-06-27
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Conversation viewer: large subagent threads now render an internally windowed slice (centered on a deep-linked / found / pinned turn) instead of mounting the entire thread, with "Show N earlier/later" and "Show all" controls to grow it on demand — a deep-link into a giant subagent no longer renders a ~106k-node DOM (#239).
|
|
18
|
+
- Conversation comparison view: the A→B metrics strip no longer clips the widest "A → B" value pairs (e.g. a high cost or long duration) at desktop widths — on a smaller desktop window the discovery rail stays mounted and narrows the strip, which the previous viewport-based reflow couldn't detect, so the six fixed columns truncated a trailing digit (`$337.05 → $169…`); the strip now reflows on its own width to a clean 3×2 (and 2-up when narrower) so every value stays fully visible (#240).
|
|
19
|
+
- Conversation viewer: a deep-linked turn now re-lands after a browser reload — reloading a deep-linked conversation URL (one shared or bookmarked deep inside a large session) used to leave you stranded at the previous scroll position with the target turn's owning subagent collapsed and never scrolled to, because the browser restored the prior scroll offset on top of the deep-link's own positioning and that restore landed after the reader had finished settling; the viewer now opts out of the browser's native scroll restoration so the deep-link is always the sole thing positioning the viewport and reliably re-lands on its target (#241).
|
|
20
|
+
- Conversation comparison view: the A→B metrics strip now stays fully un-clipped even at the very narrowest widths — in the 650–720px desktop band the mounted discovery rail can squeeze the strip below its two-column floor, and the same squeeze happens on sub-343px phones, where the widest "A → B" pairs (a high cost, a long duration) could still ellipsize a trailing character or two; the strip now drops to a single full-width column in that band so every value is shown in full, finishing the container-width reflow started in #240 (#242).
|
|
21
|
+
|
|
8
22
|
## [1.56.0] - 2026-06-26
|
|
9
23
|
|
|
10
24
|
### Fixed
|
|
@@ -799,19 +799,51 @@ def _session_cost_map(conn, session_ids):
|
|
|
799
799
|
return costs
|
|
800
800
|
|
|
801
801
|
|
|
802
|
+
def _models_main_first(rows):
|
|
803
|
+
"""Order distinct non-null models so MAIN-SESSION models sort first, then
|
|
804
|
+
models that appear ONLY in subagent / sidechain threads; alphabetical within
|
|
805
|
+
each group. ``rows`` is an iterable of ``(model, source_path, is_sidechain)``.
|
|
806
|
+
|
|
807
|
+
A model is "main" iff it appears in at least one row that is neither a
|
|
808
|
+
subagent file (``_subagent_key(source_path) is None``) nor a sidechain
|
|
809
|
+
(``not is_sidechain``) — the exact negation of the subagent/sidechain
|
|
810
|
+
predicate used elsewhere in this module. A model used in BOTH the main
|
|
811
|
+
session AND a subagent counts as MAIN (it's a model the user actually drove
|
|
812
|
+
the session with). This is what makes the reader header + browse-rail chip
|
|
813
|
+
surface the real primary model (e.g. opus) first when the main session
|
|
814
|
+
spawned haiku subagents, rather than the alphabetical-first model — pre-fix
|
|
815
|
+
a plain ``sorted()`` ranked ``haiku`` ahead of ``opus`` (#243)."""
|
|
816
|
+
main, sub = set(), set()
|
|
817
|
+
for model, source_path, is_sidechain in rows:
|
|
818
|
+
if not model:
|
|
819
|
+
continue
|
|
820
|
+
if _subagent_key(source_path) is None and not is_sidechain:
|
|
821
|
+
main.add(model)
|
|
822
|
+
else:
|
|
823
|
+
sub.add(model)
|
|
824
|
+
return sorted(main) + sorted(sub - main)
|
|
825
|
+
|
|
826
|
+
|
|
802
827
|
def _session_models_map(conn, session_ids):
|
|
803
|
-
"""{session_id:
|
|
828
|
+
"""{session_id: models ordered MAIN-session-first (subagent/sidechain-only
|
|
829
|
+
models last), alphabetical within each group — see _models_main_first}."""
|
|
804
830
|
out = {sid: [] for sid in session_ids}
|
|
805
831
|
if not session_ids:
|
|
806
832
|
return out
|
|
807
833
|
placeholders = ",".join("?" for _ in session_ids)
|
|
808
834
|
sql = (
|
|
809
|
-
"SELECT DISTINCT session_id, model
|
|
810
|
-
"
|
|
811
|
-
"
|
|
835
|
+
"SELECT DISTINCT session_id, model, source_path, is_sidechain "
|
|
836
|
+
"FROM conversation_messages "
|
|
837
|
+
"WHERE session_id IN (%s) AND model IS NOT NULL AND model != ''"
|
|
838
|
+
% placeholders
|
|
812
839
|
)
|
|
813
|
-
|
|
814
|
-
|
|
840
|
+
per_session = {}
|
|
841
|
+
for sid, model, source_path, is_sidechain in conn.execute(
|
|
842
|
+
sql, list(session_ids)):
|
|
843
|
+
per_session.setdefault(sid, []).append(
|
|
844
|
+
(model, source_path, is_sidechain))
|
|
845
|
+
for sid, rows in per_session.items():
|
|
846
|
+
out[sid] = _models_main_first(rows)
|
|
815
847
|
return out
|
|
816
848
|
|
|
817
849
|
|
|
@@ -1764,7 +1796,9 @@ def get_conversation(conn, session_id, *, after=None, before=None, tail=False,
|
|
|
1764
1796
|
"started_utc": logical[0][2],
|
|
1765
1797
|
"last_activity_utc": logical[-1][2],
|
|
1766
1798
|
"cost_usd": header_cost,
|
|
1767
|
-
|
|
1799
|
+
# #243: main-session models first (r[6]=model, r[12]=source_path,
|
|
1800
|
+
# r[9]=is_sidechain) so opus outranks a haiku subagent, not sorted().
|
|
1801
|
+
"models": _models_main_first((r[6], r[12], r[9]) for r in logical),
|
|
1768
1802
|
"last_anchor": last_anchor,
|
|
1769
1803
|
"items": [],
|
|
1770
1804
|
"subagent_meta": subagent_meta,
|
|
@@ -1814,7 +1848,9 @@ def get_conversation(conn, session_id, *, after=None, before=None, tail=False,
|
|
|
1814
1848
|
|
|
1815
1849
|
first = logical[0]
|
|
1816
1850
|
last = logical[-1]
|
|
1817
|
-
models
|
|
1851
|
+
# #243: main-session models first (r[6]=model, r[12]=source_path,
|
|
1852
|
+
# r[9]=is_sidechain) so opus outranks a haiku subagent, not sorted().
|
|
1853
|
+
models = _models_main_first((r[6], r[12], r[9]) for r in logical)
|
|
1818
1854
|
return {
|
|
1819
1855
|
"session_id": session_id,
|
|
1820
1856
|
"title": _title,
|