Skip to content

feat: publish citations + dashboard status fix + reflexio 0.2.17#10

Merged
yyiilluu merged 2 commits intomainfrom
feat/publish-citations-and-dashboard-status
Apr 28, 2026
Merged

feat: publish citations + dashboard status fix + reflexio 0.2.17#10
yyiilluu merged 2 commits intomainfrom
feat/publish-citations-and-dashboard-status

Conversation

@yyiilluu
Copy link
Copy Markdown
Contributor

@yyiilluu yyiilluu commented Apr 28, 2026

Summary

  • Wire local cited_items onto the publish-time InteractionData.citations field so reflexio (v0.2.17+) can drive playbook/profile reflection from real Assistant citations.
  • Restrict citation and tools_used attachment to Assistant turns only — User turns no longer carry citation metadata into the publish payload.
  • Align the dashboard with reflexio's Status StrEnum + response_model_exclude_none wire shape — values are lowercase ("archived", "pending") and CURRENT rows arrive as null/missing, not "CURRENT".
  • Bump reflexio submodule to v0.2.17.
  • Lower per-session interaction-cleanup thresholds (1000/500 → 500/200) so claude-smart users don't pay the higher server-deployment defaults.

Changes

Citation wiring (server-bound)

  • plugin/src/claude_smart/state.py — new _to_wire_citations helper maps {id, kind, title, real_id} (local) → {kind, real_id, tag, title} (wire Citation shape). unpublished_slice now folds resolvable citations onto the Assistant turn under citations; entries without real_id or with an unsupported kind are dropped (the server has a Literal["playbook", "profile"]).
  • plugin/src/claude_smart/state.py — citation and tools_used attachment is now guarded by role == "Assistant". User turns retain their plain shape and never carry citation metadata, even if cited_items is somehow present on the record. Input checks in _to_wire_citations also reject non-string id/title/real_id defensively.
  • tests/test_state.py — adds coverage for the mapping happy path, omitted-when-empty behavior, invalid kind filtering, missing real_id rejection, and non-list / non-dict input safety.

Dashboard status fix

  • plugin/dashboard/lib/types.ts — narrow PlaybookStatus / ProfileStatus to "pending" | "archived"; document the null-for-current invariant.
  • plugin/dashboard/lib/status.ts — new shared statusLabel helper. Consolidates the three previously duplicated copies in playbooks/profiles pages.
  • plugin/dashboard/app/dashboard/page.tsx — filter CURRENT playbooks via status == null (matches the wire), drop the dead "CURRENT" string compare.
  • plugin/dashboard/app/playbooks/page.tsx, app/playbooks/[id]/page.tsx, app/profiles/[id]/page.tsx — import the shared helper; lowercase comparisons replace the old uppercase ones.

Cleanup-threshold tuning

  • plugin/scripts/backend-service.shINTERACTION_CLEANUP_THRESHOLD 1000 → 500 and INTERACTION_CLEANUP_DELETE_COUNT 500 → 200. Cap of 500 interactions, evict oldest 200, keep 300.

Submodule

  • reflexiov0.2.17 (single release commit, contains the Citation wire model).

Test Plan

  • ruff check + ruff format clean on state.py and tests/test_state.py.
  • pyright clean on the same two files.
  • npx tsc --noEmit clean across the dashboard.
  • npx biome check clean on all touched dashboard files (exit 0).
  • New unit tests in tests/test_state.py exercise:
    • mapping happy path with both playbook and profile citations
    • empty / unresolvable cited items omit the citations key entirely
    • invalid kind and missing real_id are filtered
    • non-list / non-dict inputs return [] without raising
  • Manual: stop hook → verify a real Assistant turn with cs-cite calls shows up in the publish payload with a populated citations array, and that User turns in the same payload have no citations key.
  • Manual: dashboard pages render archived / pending / current playbooks and profiles correctly with the new wire shape.

Summary by CodeRabbit

  • New Features

    • Citations are now included when publishing interactions.
  • Improvements

    • Standardized status representation across playbooks and profiles for consistency.
    • Optimized backend interaction cleanup with adjusted default thresholds for better performance.
  • Tests

    • Expanded test coverage for citation handling in interactions.

…to 0.2.17

- Map local cited_items onto InteractionData.citations on the publish wire so
  reflexio can drive playbook/profile reflection from real Assistant citations.
- Align dashboard with reflexio's lowercase-StrEnum + null-for-current wire
  shape: narrow PlaybookStatus/ProfileStatus types, treat null as CURRENT,
  consolidate the three duplicated statusLabel helpers into lib/status.ts.
- Lower per-session interaction-cleanup thresholds (1000/500 -> 500/200) for
  shorter-lived claude-smart users.
- Bump reflexio submodule to v0.2.17.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

Consolidates status handling into a shared dashboard status utility, narrows wire-format status types to lowercase, adds citation mapping into assistant turn payloads, adjusts backend cleanup defaults, extends citation tests, and updates the reflexio submodule pointer.

Changes

Cohort / File(s) Summary
Dashboard Status Consolidation
plugin/dashboard/app/dashboard/page.tsx, plugin/dashboard/app/playbooks/[id]/page.tsx, plugin/dashboard/app/playbooks/page.tsx, plugin/dashboard/app/profiles/[id]/page.tsx
Replaced file-local status helpers with statusLabel import from @/lib/status; dashboard/page.tsx now treats status == null as the sole indicator of "CURRENT".
Shared Status Library
plugin/dashboard/lib/status.ts
New exported StatusLabel type and statusLabel() function that normalizes nullable/lowercase wire statuses into `"CURRENT"
Type Definition Updates
plugin/dashboard/lib/types.ts
Adjusted wire-format types: PlaybookStatus and ProfileStatus changed to lowercase `"pending"
Citations in Turn Payloads
plugin/src/claude_smart/state.py
Added mapping from local cited_items into wire-format citations ({kind, real_id, tag, title}); filters malformed/unsupported entries and includes citations only when non-empty.
Citation Tests
tests/test_state.py
Expanded tests to verify citation mapping behavior, filtering rules, omission when empty/unresolvable, and edge cases (non-list, mixed lists).
Backend Configuration
plugin/scripts/backend-service.sh
Lowered default cleanup thresholds: INTERACTION_CLEANUP_THRESHOLD 1000→500, INTERACTION_CLEANUP_DELETE_COUNT 500→200.
Submodule Update
reflexio
Updated git submodule pointer to new commit hash.

Sequence Diagram(s)

sequenceDiagram
    participant Unpub as Unpublished Slice
    participant Mapper as _to_wire_citations
    participant Turn as Turn Object
    participant Payload as Published Payload

    Unpub->>Unpub: Gather cited_items (local)
    Unpub->>Mapper: _to_wire_citations(cited_items)
    Mapper->>Mapper: Validate each element (dict, kind allowed, non-empty real_id)
    Mapper->>Mapper: Transform {id -> tag, real_id, kind, title}
    Mapper-->>Unpub: Return mapped citations[]
    alt citations non-empty
        Unpub->>Turn: Attach citations field to assistant turn
    else citations empty
        Unpub->>Turn: Do not attach citations field
    end
    Turn->>Payload: Include turn in published payload
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly Related PRs

Poem

🐰 I hopped through code to tidy each state,
Nulls now mean current, labels coordinate.
I stitched cited items into payloads neat,
Lowered the thresholds, tests now repeat.
Hooray — my little mapping made the build complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title comprehensively covers the three main objectives: citation publishing, dashboard status alignment, and reflexio version bump. It is clear, specific, and accurately summarizes the primary changes.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/publish-citations-and-dashboard-status

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
plugin/src/claude_smart/state.py (1)

37-37: Consider centralizing citation-kind constants.

_VALID_CITATION_KINDS duplicates the same domain contract enforced in plugin/src/claude_smart/cs_cite.py:rank_id. A shared constant/module would prevent future drift when new kinds are added.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/src/claude_smart/state.py` at line 37, The file defines
_VALID_CITATION_KINDS which duplicates the contract enforced by cs_cite.rank_id;
centralize this by moving the set of valid citation kinds into a single shared
symbol (e.g., export VALID_CITATION_KINDS from a new or existing module such as
claude_smart.constants or from plugin/src/claude_smart/cs_cite.py) and update
state.py to import and use that shared VALID_CITATION_KINDS instead of its own
_VALID_CITATION_KINDS to avoid drift when kinds change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugin/src/claude_smart/state.py`:
- Around line 196-206: The current block allows non-string truthy values for
real_id/tag/title to be emitted; ensure the emitted payload only contains
strings by validating and coercing values before appending: check real_id is a
non-empty string (reject or continue if not a str), coerce tag and title to
strings using safe conversion (defaulting to "" when None or non-primitive), and
keep the existing kind validation against _VALID_CITATION_KINDS; update the
logic around the real_id, tag, title variables used in the out.append call so
only validated/converted strings are written to the payload.
- Around line 264-266: The code computes citations from rec.get("cited_items")
for every turn, which can attach citations to user turns; restrict this to
Assistant turns only by checking the role before calling _to_wire_citations.
Update the logic around rec / turn (the block that sets citations and uses
_to_wire_citations) to first verify rec.get("role") == "assistant" (or
turn.get("role") == "assistant") and only then compute citations and set
turn["citations"]; leave other behavior unchanged.

In `@reflexio`:
- Line 1: The reflexio submodule is currently pinned to the unreleased main
commit 9143d1bf74584d0ecced69216c5b50773363c8e0 instead of the v0.2.17 release
commit a99ff1df33ed586125b277010aef1ab282fb1421; either update the submodule
pointer to the v0.2.17 tag/commit (reset the reflexio submodule to
a99ff1df33ed586125b277010aef1ab282fb1421, commit the change) so the repo
consumes the released wire-model, or if using main is intentional, add a clear
PR note and repository CHANGELOG entry stating that reflexio is intentionally
pinned to main (9143d1bf...) and accept the risk of unreleased changes.

---

Nitpick comments:
In `@plugin/src/claude_smart/state.py`:
- Line 37: The file defines _VALID_CITATION_KINDS which duplicates the contract
enforced by cs_cite.rank_id; centralize this by moving the set of valid citation
kinds into a single shared symbol (e.g., export VALID_CITATION_KINDS from a new
or existing module such as claude_smart.constants or from
plugin/src/claude_smart/cs_cite.py) and update state.py to import and use that
shared VALID_CITATION_KINDS instead of its own _VALID_CITATION_KINDS to avoid
drift when kinds change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b432268-9a21-44cc-bcb1-54d47e069219

📥 Commits

Reviewing files that changed from the base of the PR and between e412c32 and 4006e01.

📒 Files selected for processing (10)
  • plugin/dashboard/app/dashboard/page.tsx
  • plugin/dashboard/app/playbooks/[id]/page.tsx
  • plugin/dashboard/app/playbooks/page.tsx
  • plugin/dashboard/app/profiles/[id]/page.tsx
  • plugin/dashboard/lib/status.ts
  • plugin/dashboard/lib/types.ts
  • plugin/scripts/backend-service.sh
  • plugin/src/claude_smart/state.py
  • reflexio
  • tests/test_state.py

Comment thread plugin/src/claude_smart/state.py
Comment thread plugin/src/claude_smart/state.py Outdated
Comment thread reflexio
@@ -1 +1 @@
Subproject commit d9e000100157bcb59efd7390883e828b1598291e
Subproject commit 9143d1bf74584d0ecced69216c5b50773363c8e0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Confirm the recorded submodule pointer for ./reflexio
git submodule status

# 2) If the submodule is checked out in this workspace, confirm its HEAD matches the pointer
if [ -d "reflexio/.git" ] || [ -d "reflexio" ]; then
  (cd reflexio && git rev-parse HEAD || true)
fi

# 3) If the submodule has a version file/package.json, print the version info
if [ -f "reflexio/package.json" ]; then
  node -p "require('./reflexio/package.json').version"
fi
if [ -f "reflexio/pyproject.toml" ]; then
  rg -n "version\s*=" reflexio/pyproject.toml || true
fi

Repository: ReflexioAI/claude-smart

Length of output: 159


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if reflexio is a git submodule and inspect its remote
if [ -d "reflexio/.git" ] || [ -d "reflexio" ]; then
  echo "=== Reflexio git info ==="
  
  # Try to get remote URL and recent commits
  (cd reflexio && git remote -v 2>/dev/null || echo "No remote info available")
  
  # Check for version tags or release info near the new commit
  echo ""
  echo "=== Checking commit 9143d1bf74584d0ecced69216c5b50773363c8e0 ==="
  (cd reflexio && git show --quiet 9143d1bf74584d0ecced69216c5b50773363c8e0 2>/dev/null || echo "Commit not found locally")
  
  # List recent tags in the submodule
  echo ""
  echo "=== Recent tags in reflexio ==="
  (cd reflexio && git tag -l | tail -20 || true)
  
  # Show commit log to see version history
  echo ""
  echo "=== Recent commits in reflexio ==="
  (cd reflexio && git log --oneline -20 || true)
else
  echo "reflexio directory not accessible as a git repo"
fi

Repository: ReflexioAI/claude-smart

Length of output: 475


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== .gitmodules content ==="
if [ -f ".gitmodules" ]; then
  cat -n .gitmodules
else
  echo ".gitmodules not found"
fi

echo ""
echo "=== Git config submodule.reflexio.url ==="
git config --file .gitmodules submodule.reflexio.url || echo "No submodule.reflexio.url found"

echo ""
echo "=== Git index/HEAD reference to reflexio ==="
git ls-files -s | grep reflexio || echo "No reflexio entry in index"

echo ""
echo "=== Check if reflexio is listed as a regular directory vs submodule ==="
git status reflexio 2>&1 || true

Repository: ReflexioAI/claude-smart

Length of output: 995


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check previous reflexio commit (before this PR) ==="
# Find what the reflexio submodule pointed to before this change
git log --oneline -20 -- reflexio || echo "No history available"

echo ""
echo "=== Fetch external reflexio repo info to verify the commit ==="
# Use git ls-remote to check the external repo without cloning
git ls-remote https://github.com/ReflexioAI/reflexio.git | grep -E "(9143d1bf|refs/tags/|HEAD)" | head -20

Repository: ReflexioAI/claude-smart

Length of output: 755


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check current HEAD for reflexio pointer ==="
git show HEAD:.gitmodules | grep -A2 'submodule "reflexio"' || echo "Unable to read from HEAD"

echo ""
echo "=== Get the diff showing what reflexio commit changed from/to ==="
git diff HEAD~1 HEAD -- reflexio 2>/dev/null | head -40 || echo "No previous commit or cannot diff"

echo ""
echo "=== Direct check: what's the reflexio entry in HEAD vs HEAD~1 ==="
echo "HEAD: $(git ls-tree HEAD reflexio | awk '{print $3}')"
git ls-tree HEAD~1 reflexio 2>/dev/null | awk '{print "HEAD~1: " $3}' || echo "HEAD~1 not available"

Repository: ReflexioAI/claude-smart

Length of output: 441


Submodule pointer uses unreleased reflexio code, not the v0.2.17 release.

The PR updates reflexio to commit 9143d1bf74584d0ecced69216c5b50773363c8e0, which is the current main branch HEAD of the external reflexio repository. However, the v0.2.17 release tag points to a different commit (a99ff1df33ed586125b277010aef1ab282fb1421). This means the main repo will consume unreleased wire-model changes from reflexio's main branch, not the finalized v0.2.17 release.

Clarify intent: Is using the unreleased main branch intentional (accepted risk), or should this pin to the v0.2.17 tag commit instead to match the claimed version?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@reflexio` at line 1, The reflexio submodule is currently pinned to the
unreleased main commit 9143d1bf74584d0ecced69216c5b50773363c8e0 instead of the
v0.2.17 release commit a99ff1df33ed586125b277010aef1ab282fb1421; either update
the submodule pointer to the v0.2.17 tag/commit (reset the reflexio submodule to
a99ff1df33ed586125b277010aef1ab282fb1421, commit the change) so the repo
consumes the released wire-model, or if using main is intentional, add a clear
PR note and repository CHANGELOG entry stating that reflexio is intentionally
pinned to main (9143d1bf...) and accept the risk of unreleased changes.

User turns shouldn't carry citations or tool_used metadata; restrict the
citation/pending_tools attachment to Assistant turns and harden the
_to_wire_citations input checks against non-string id/title/real_id.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
plugin/src/claude_smart/state.py (1)

202-210: ⚠️ Potential issue | 🟠 Major

Reject malformed id/title instead of emitting empty fields.

At Line 208 and Line 209, malformed values are normalized to "", so invalid citations still get emitted. That weakens payload quality and can produce blank tags/titles downstream.

Proposed fix
         tag = item.get("id")
         title = item.get("title")
+        if not isinstance(tag, str) or not tag:
+            continue
+        if not isinstance(title, str):
+            continue
         out.append(
             {
                 "kind": kind,
                 "real_id": real_id,
-                "tag": tag if isinstance(tag, str) else "",
-                "title": title if isinstance(title, str) else "",
+                "tag": tag,
+                "title": title,
             }
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/src/claude_smart/state.py` around lines 202 - 210, The code currently
normalizes malformed tag/title to empty strings which emits invalid entries;
instead validate tag and title before appending: check that tag and title are
instances of str (and non-empty if required) and if either is invalid skip/omit
the entire out.append for that item (or return/continue) so only well-formed
entries with valid tag/title (use the existing variables tag, title, kind,
real_id) are emitted; update the logic around the out.append call to perform
this validation and optionally log or count skipped items.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@plugin/src/claude_smart/state.py`:
- Around line 202-210: The code currently normalizes malformed tag/title to
empty strings which emits invalid entries; instead validate tag and title before
appending: check that tag and title are instances of str (and non-empty if
required) and if either is invalid skip/omit the entire out.append for that item
(or return/continue) so only well-formed entries with valid tag/title (use the
existing variables tag, title, kind, real_id) are emitted; update the logic
around the out.append call to perform this validation and optionally log or
count skipped items.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cb28465d-b2a7-4865-a57c-042cc215a986

📥 Commits

Reviewing files that changed from the base of the PR and between 4006e01 and fc2f6b7.

📒 Files selected for processing (1)
  • plugin/src/claude_smart/state.py

@yyiilluu yyiilluu merged commit 4d5e0d5 into main Apr 28, 2026
5 checks passed
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.

1 participant