Skip to content

cockpit: unified ClassView card + guid2 McClelland decode (restores PR #84 code, never merged)#87

Merged
AdaWorldAPI merged 3 commits into
mainfrom
claude/mcclelland-decode
Jul 6, 2026
Merged

cockpit: unified ClassView card + guid2 McClelland decode (restores PR #84 code, never merged)#87
AdaWorldAPI merged 3 commits into
mainfrom
claude/mcclelland-decode

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Jul 6, 2026

Copy link
Copy Markdown
Owner

⚠️ This restores the code PR #84 never actually merged

PR #84's merge commit (cb8a8419) has the plan commit 04d82236 as 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 reached main; only the plan doc did. This PR brings that code forward, reconciled onto post-#86 / post-a857ac2d main, and adds the guid2 person decode that #86 unblocked. (Process fix applied: the branch tip was verified == local HEAD before this PR was opened — the check whose absence caused the silent miss.)

What lands (3 commits)

  1. b2e346f0 — unified ClassView card (restored from the unmerged 66179d0a, reconciled with the a857ac2d one-class collapse): OSINT_DECODE builder, ValueCardRow, V3 name index, guid/name/fragment params, the generic /api/card.html?guid= route sharing one code path with the OSINT route, and the bare osint_card_fragment.html.
  2. 4fc49380 — guid2 McClelland decode (1-indexed per data: rebake osint-v3 — store GUID tiers in declared field order (hi/lo fix) #86): OSINT_DECODE slot-2 from mcclelland_vocab (v → vocab[v-1]); the _ padding position advances the counter but inserts nothing, and OSINT_PERSON_FIELDS (5 fields) means facet_rows never emits it. Byte 0 / out-of-vocab stay None.
  3. 0deb50ad — React node-detail popup (restored from the unmerged 3f623795): click a node → fetch the fragment → schema-blind injection into a positioned popup; /osint.soa dev-proxy line.

Verification

  • Decode against current main data (python probe replicating the Rust builder byte-for-byte): slot-1 609/611 nodes fully decode — the 2 misses are exactly the data: rebake osint-v3 — store GUID tiers in declared field order (hi/lo fix) #86-documented codebook gaps (DreamSecurity MLType=30, Shoebox civicUse=17); slot-2 133/133 persons (5 fields + padding=0).
  • Isolated Rust harness (vendors the OSINT_DECODE builder, real codebook, serde only): compiles and passes every committed unit-test assertion — the [4,1,2,3,2] → IV/nPow/STATUS/ACTIONAL/STATUS vector 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.
  • Full cockpit-server compile is Railway-gated — the transitive deno_core → v8 static-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

    • Added a clickable node details popup in the OSINT graph, with loading and error states.
    • Graph node selection now shows persistent details while keeping the existing reasoning view.
    • Introduced a new card view endpoint that returns a compact HTML fragment for node details.
  • Bug Fixes

    • Improved request handling so node detail loads are safely cancelled when the popup changes or closes.
    • Updated proxy routing so the OSINT graph can reach the new backend endpoint correctly.

claude added 3 commits July 6, 2026 12:07
…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
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a clicked-node "ClassView" popup to the OSINT graph UI, fetching server-rendered HTML from a new generic /api/card.html endpoint. The backend introduces unified card resolution combining V3 node index and codebook decode maps, value-projected rows, a fragment template, and expanded tests.

Changes

OSINT ClassView Popup and Unified Card Rendering

Layer / File(s) Summary
V3 index and decode map loading
crates/cockpit-server/src/osint_classview.rs
Adds lazily-loaded OSINT_V3_INDEX and OSINT_DECODE from disk JSON, GUID hex parsing, and data path resolution.
Unified card query resolution
crates/cockpit-server/src/osint_classview.rs
Extends CardQuery with guid/name/fragment, adds ValueCardRow and resolve_card for name/guid/schema-based resolution.
Shared response rendering and routes
crates/cockpit-server/src/osint_classview.rs, crates/cockpit-server/src/main.rs
Adds render_card_response, updates osint_card_html_handler, implements generic_card_handler, wires new /api/card.html route.
Card templates with value column
crates/cockpit-server/templates/osint_card.html, crates/cockpit-server/templates/osint_card_fragment.html
Conditionally renders a value/decoded column; adds a new fragment template.
Unified rendering tests
crates/cockpit-server/src/osint_classview.rs
Updates/extends tests for facet binding, slot selection, GUID parsing, decoding, and route regression.
Frontend NodeDetailPopup and click wiring
cockpit/src/OsintGraph.tsx, cockpit/vite.config.ts
Adds NodeDetailPopup fetching card HTML by label, detail state, click-handler updates, popup rendering, and vite proxy entry.

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
Loading

Possibly related PRs

  • AdaWorldAPI/q2#70: Both PRs implement/extend the OSINT ClassView HTML card API in osint_classview.rs and main.rs, which this PR's popup now consumes.
  • AdaWorldAPI/q2#72: This PR's resolver loads osint_v3_nodes.json/osint_v3_codebook.json, the same data the retrieved PR rebakes and updates.

Poem

A click, a hop, a card appears,
Decoded facets, no more fears.
V3 index, codebook bright,
Fragments rendered, just so light. 🐇
Through wires and routes I bound with glee,
A popup born for you and me!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: unified ClassView card rendering and guid2 McClelland decoding.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@AdaWorldAPI AdaWorldAPI merged commit e045829 into main Jul 6, 2026
4 of 5 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
crates/cockpit-server/templates/osint_card_fragment.html (1)

9-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate 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 when ValueCardRow rendering 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

#id fallback label produces an unresolvable card fetch.

When soa.labels[i] is empty, detail.label becomes #<id>, and NodeDetailPopup fetches ?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

📥 Commits

Reviewing files that changed from the base of the PR and between 5293613 and 0deb50a.

📒 Files selected for processing (6)
  • cockpit/src/OsintGraph.tsx
  • cockpit/vite.config.ts
  • crates/cockpit-server/src/main.rs
  • crates/cockpit-server/src/osint_classview.rs
  • crates/cockpit-server/templates/osint_card.html
  • crates/cockpit-server/templates/osint_card_fragment.html

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0deb50a. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants