cockpit: unified ClassView card + guid2 McClelland decode (restores PR #84 code, never merged)#87
Conversation
…ic /api/card.html route Restores PR #84's server-side code (66179d0a on claude/classview-unified- render) which was never pushed before the PR merged — the merge carried only the plan commit (04d8223). Applied onto post-#86/post-a857ac2d main: the reference commit was itself authored after a857ac2 (one-class collapse, canon-high 0x0701_1000), so its module docs, slot rule, and classid tests already match current main's model; the cherry-pick applied clean with zero drift in crates/cockpit-server/ or templates/ since the branch base. What lands (crates/cockpit-server/ only): - osint_classview.rs: ValueCardRow (unified schema/value row, optional value+decoded), OSINT_V3_INDEX (name/id -> guid1/guid2 from data/osint-v3/osint_v3_nodes.json, degrades to empty when absent), OSINT_DECODE ((slot, position, byte) -> vocab name, slot 1 built mechanically from the codebook's declared tier order), parse_guid_hex, facet_of, slot_for_entry, resolve_card + resolved_rows + render_card_response (the one shared resolution/render core), and generic_card_handler (400s on missing/malformed guid). - main.rs: registers GET /api/card.html alongside the OSINT routes. - templates/osint_card_fragment.html: bare <table> fragment (fragment=1) for the React popup container, zero CSS. - templates/osint_card.html: optional value column via has_values — byte-identical output for the pre-existing class/mask-only requests. The original commit's decode-coverage caveat is now resolved by the #86 rebake (data stored in declared field order): the mechanical slot-1 mapping verified empirically on current main data at 609/611 guid1 facets fully in-vocabulary; the two misses are known codebook gaps (Shoebox civicUse=17, DreamSecurity MLType=30), not order errors. guid2 (person/McClelland) remains undecoded in THIS commit; the 1-indexed slot-2 decode ruled by #86 lands in the follow-up commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
Completes the person-card value decode that PR #84 deliberately left off pending the indexing ruling the #86 rebake settled. mcclelland_vocab is a plain array per field with no explicit value labels; #86 proved empirically that person tiers are 1-indexed (byte v -> vocab[v-1]): across all 133 persons every field's observed max equals its vocab length, and 0-indexing would overflow 26/133. OSINT_DECODE now populates slot-2 from guid2_tiers order (HEEL stage:need, HIP receptor:rubicon, TWIG motive:_) over mcclelland_vocab, 1-indexed. The `_` padding position advances the counter but inserts nothing (no vocab), and OSINT_PERSON_FIELDS declares only positions 0..=4 so facet_rows never emits the padding byte. Byte 0 and out-of-vocab bytes stay None — never guessed. Verified against current main data (python probe replicating the builder): slot-2 133/133 persons fully decode (5 fields, padding=0); slot-1 609/611 (the 2 misses are the #86-documented codebook gaps: DreamSecurity MLType=30, Shoebox civicUse=17). Two unit tests added: full decode path (facet [4,1,2,3,2,0] -> IV/nPow/STATUS/ACTIONAL/STATUS, padding absent) and the 1-indexed boundaries (v=0 undecoded, v=len last entry, v=len+1 overflow). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
Click a node in the OSINT graph scene to show a positioned popup that fetches /api/osint/card.html?name=<label>&fragment=1 and injects the returned HTML fragment via dangerouslySetInnerHTML. The client is schema-blind: it never branches on node class, it just injects whatever <table class="cv-card"> the server sends (rows carry .r/.l/.p/.v cell classes, styled by a popup-scoped <style> block) — new node classes need zero cockpit rebuild. - NodeDetailPopup component (mirrors ReasonBox's overlay conventions: fixed position, dark cockpit palette, monospace, ✕ close). - New `detail` state wired into the vis-network click handler alongside the existing reasonFrom() call; empty-space clicks clear it. - vite dev proxy gains /osint.soa -> 127.0.0.1:2718 so `npm run dev` can load the live scene (was previously only served in prod builds). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📝 WalkthroughWalkthroughThis PR adds a clicked-node "ClassView" popup to the OSINT graph UI, fetching server-rendered HTML from a new generic ChangesOSINT ClassView Popup and Unified Card Rendering
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant OsintGraph
participant NodeDetailPopup
participant CardHtmlAPI as "/api/osint/card.html"
participant OsintClassview as osint_classview resolver
User->>OsintGraph: click node
OsintGraph->>OsintGraph: reasonFrom(node)
OsintGraph->>OsintGraph: set detail = label
OsintGraph->>NodeDetailPopup: render with label
NodeDetailPopup->>CardHtmlAPI: fetch card.html?name=label
CardHtmlAPI->>OsintClassview: resolve_card(query)
OsintClassview->>OsintClassview: lookup OSINT_V3_INDEX / OSINT_DECODE
OsintClassview-->>CardHtmlAPI: render_card_response(rows)
CardHtmlAPI-->>NodeDetailPopup: HTML fragment
NodeDetailPopup-->>User: display card via dangerouslySetInnerHTML
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
crates/cockpit-server/templates/osint_card_fragment.html (1)
9-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate row/header markup with
osint_card.html.Lines 10-11 here are identical to lines 17-18 in
osint_card.html. Consider extracting the<tr>header and row-loop into a shared partial (Askama{% include %}) to keep the two templates from drifting whenValueCardRowrendering changes.♻️ Suggested partial extraction
Create
osint_card_rows.html:<tr><td class="r">role</td><td class="l">field</td><td class="p">predicate</td>{% if has_values %}<td class="v">value</td>{% endif %}</tr> {% for row in rows %}<tr><td class="r">{{ row.role }}</td><td class="l">{{ row.label }}</td><td class="p">{{ row.predicate }}</td>{% if has_values %}<td class="v">{% if let Some(v) = row.value %}{{ v }}{% if let Some(d) = row.decoded %} ({{ d }}){% endif %}{% endif %}</td>{% endif %}</tr> {% endfor %}-<table> -<tr><td class="r">role</td><td class="l">field</td><td class="p">predicate</td>{% if has_values %}<td class="v">value</td>{% endif %}</tr> -{% for row in rows %}<tr>...{% endfor %}</table> +<table> +{% include "osint_card_rows.html" %} +</table>(apply the equivalent include in
osint_card_fragment.html)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/cockpit-server/templates/osint_card_fragment.html` around lines 9 - 12, The `osint_card_fragment.html` template duplicates the same header row and `rows` rendering logic already used in `osint_card.html`, so extract that shared `<tr>` plus `{% for row in rows %}` block into a common Askama partial and include it from both templates. Create a reusable partial for the `ValueCardRow` rendering, then update `osint_card_fragment.html` and `osint_card.html` to reference it so future changes to `row.value`/`row.decoded` formatting stay in sync.cockpit/src/OsintGraph.tsx (1)
966-972: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
#idfallback label produces an unresolvable card fetch.When
soa.labels[i]is empty,detail.labelbecomes#<id>, andNodeDetailPopupfetches?name=%23<id>, which the name-based resolver can't match — the popup will always render "card load failed" for unlabeled nodes. Consider suppressing the popup (or fetching by guid) when no real label exists.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cockpit/src/OsintGraph.tsx` around lines 966 - 972, The fallback label in the node selection flow is causing `NodeDetailPopup` to request an unresolvable name for unlabeled nodes. Update the `params.nodes.length` branch in `OsintGraph` so `setDetail` is only called with a real label from `soa.labels[i]`, and suppress the popup or switch to a guid-based lookup when the label is missing instead of using `#${i}`. Keep the behavior localized around `reasonFrom`, `setDetail`, and the popup-triggering detail state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@cockpit/src/OsintGraph.tsx`:
- Around line 966-972: The fallback label in the node selection flow is causing
`NodeDetailPopup` to request an unresolvable name for unlabeled nodes. Update
the `params.nodes.length` branch in `OsintGraph` so `setDetail` is only called
with a real label from `soa.labels[i]`, and suppress the popup or switch to a
guid-based lookup when the label is missing instead of using `#${i}`. Keep the
behavior localized around `reasonFrom`, `setDetail`, and the popup-triggering
detail state.
In `@crates/cockpit-server/templates/osint_card_fragment.html`:
- Around line 9-12: The `osint_card_fragment.html` template duplicates the same
header row and `rows` rendering logic already used in `osint_card.html`, so
extract that shared `<tr>` plus `{% for row in rows %}` block into a common
Askama partial and include it from both templates. Create a reusable partial for
the `ValueCardRow` rendering, then update `osint_card_fragment.html` and
`osint_card.html` to reference it so future changes to `row.value`/`row.decoded`
formatting stay in sync.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 050398d1-5838-4802-9ee2-c91e9a4456e7
📒 Files selected for processing (6)
cockpit/src/OsintGraph.tsxcockpit/vite.config.tscrates/cockpit-server/src/main.rscrates/cockpit-server/src/osint_classview.rscrates/cockpit-server/templates/osint_card.htmlcrates/cockpit-server/templates/osint_card_fragment.html
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0deb50a. Configure here.
| PathBuf::from("../data/osint-v3").join(filename), | ||
| PathBuf::from("../../data/osint-v3").join(filename), | ||
| ]; | ||
| candidates.into_iter().find(|p| p.exists()) |
There was a problem hiding this comment.
Production image lacks V3 bake
High Severity
The popup’s decoded ClassView cards depend on osint_v3_nodes.json and osint_v3_codebook.json loaded via resolve_data_path. The slim runtime image only ships the binary and aiwar JSON, so the compile-time CARGO_MANIFEST_DIR path and cwd fallbacks never find the bake; OSINT_V3_INDEX and OSINT_DECODE stay empty and ?name= responses are schema-only without values.
Reviewed by Cursor Bugbot for commit 0deb50a. Configure here.


PR #84's merge commit (
cb8a8419) has the plan commit04d82236as its second parent — the code commits were never pushed to the branch tip before merge. So the OSINT detail popup + card-value machinery that #84 described never reachedmain; only the plan doc did. This PR brings that code forward, reconciled onto post-#86 / post-a857ac2dmain, and adds the guid2 person decode that #86 unblocked. (Process fix applied: the branch tip was verified== local HEADbefore this PR was opened — the check whose absence caused the silent miss.)What lands (3 commits)
b2e346f0— unified ClassView card (restored from the unmerged66179d0a, reconciled with thea857ac2done-class collapse):OSINT_DECODEbuilder,ValueCardRow, V3 name index,guid/name/fragmentparams, the generic/api/card.html?guid=route sharing one code path with the OSINT route, and the bareosint_card_fragment.html.4fc49380— guid2 McClelland decode (1-indexed per data: rebake osint-v3 — store GUID tiers in declared field order (hi/lo fix) #86):OSINT_DECODEslot-2 frommcclelland_vocab(v → vocab[v-1]); the_padding position advances the counter but inserts nothing, andOSINT_PERSON_FIELDS(5 fields) meansfacet_rowsnever emits it. Byte 0 / out-of-vocab stayNone.0deb50ad— React node-detail popup (restored from the unmerged3f623795): click a node → fetch the fragment → schema-blind injection into a positioned popup;/osint.soadev-proxy line.Verification
OSINT_DECODEbuilder, real codebook, serde only): compiles and passes every committed unit-test assertion — the[4,1,2,3,2] → IV/nPow/STATUS/ACTIONAL/STATUSvector and the 1-indexed boundaries (v=0 undecoded, v=len last entry, v=len+1 overflow, padding never decodes). slot-2 = 23 entries (5+3+5+5+5).cd cockpit && npm run build(tsc + vite): clean, 106 modules.cockpit-servercompile is Railway-gated — the transitivedeno_core → v8static-lib download is egress-blocked in the dev sandbox (same wall as cockpit: unified ClassView card — OSINT node detail popup + generic /api/card.html route #84's original verification). The decode logic is proven by the probe + harness above; Railway is the true module-compile gate.Once merged, the detail popup finally reaches production: guid1 system cards and guid2 person cards (stage/need/receptor/rubicon/motive — the McClelland lens) render analyst-readable values at ~609/611 + 133/133.
🤖 Generated with Claude Code
Generated by Claude Code
Summary by CodeRabbit
New Features
Bug Fixes