Skip to content

feat: Add session stage tags and auto-linking#78

Merged
bbsngg merged 3 commits intomainfrom
feat/session-stage-auto-tags
Mar 26, 2026
Merged

feat: Add session stage tags and auto-linking#78
bbsngg merged 3 commits intomainfrom
feat/session-stage-auto-tags

Conversation

@bbsngg
Copy link
Copy Markdown
Contributor

@bbsngg bbsngg commented Mar 22, 2026

Summary

  • add a normalized project/session tag model so sessions can link to multiple research stages
  • return stage tags in session APIs, surface them in the sidebar and Research Lab, and keep Session Stage Links from jumping back to the top after edits
  • auto-apply stage tags from task context and auto research runs, and prevent low-priority auto tagging from re-adding stages the user manually removed

Testing

  • npm run build
  • node --check server/database/db.js
  • node --check server/index.js
  • node --check server/utils/sessionIndex.js
  • npm run typecheck (fails due existing missing vitest types in src/components/chat/utils/tests/chatFormatting.test.ts)

Copy link
Copy Markdown
Contributor Author

@bbsngg bbsngg left a comment

Choose a reason for hiding this comment

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

Code Review

Overall this is a well-structured feature PR. The DB schema design is solid and the defensive coding (null checks, normalization, chunking) is thorough. Here are the specific comments:


1. applyStageTagsToSession is fire-and-forget (no await)

Files: server/claude-sdk.js, server/cursor-cli.js, server/gemini-cli.js, server/openai-codex.js

applyStageTagsToSession is called without await. While the function is currently synchronous (better-sqlite3 is sync), the function signature doesn't make this obvious. If it's ever made async, these calls will silently swallow errors. Consider:

  • Adding a comment that it's intentionally sync/fire-and-forget
  • Or wrapping in try/catch to be safe

2. Duplicate tag application — both at spawn start AND in recordIndexedSession

applyStageTagsToSession is called at the top of the spawn function, and then stageTagKeys/tagSource are also passed to recordIndexedSession, which calls applyStageTagsToSession again internally (server/utils/sessionIndex.js:86-95). Since appendSessionTagsByKeys uses INSERT OR IGNORE, there's no data corruption, but it's redundant work. The early call handles the case where the session already exists; the recordIndexedSession call handles new sessions. Worth adding a comment to explain this intentional dual-path.


3. SessionTag type has both snake_case and camelCase fields

File: src/types/app.ts:4-25

The SessionTag interface has both tag_key and tagKey, tag_type and tagType, etc. While this accommodates the server returning either format, it creates ambiguity in consumers. Throughout the codebase, every usage does tag.tagKey || tag.tag_key — this is fragile. Consider normalizing once at the API boundary (in parseTagRow on the server, which already does this) and only keeping the camelCase fields in the TypeScript type.


4. SidebarSessionItem.tsx — duplicated stage tag rendering block

File: src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx:132-156 and 205-226

The exact same stage tag rendering JSX is duplicated in two places (compact view and expanded view). Extract this into a small local component or variable to DRY it up.


5. Missing projectName validation in tag API endpoints

File: server/index.js:722-773

The GET /api/projects/:projectName/tags endpoint calls tagDb.ensureDefaultStageTags(projectName) on every request. This means any arbitrary projectName string will get default tags inserted. Consider checking that the project actually exists first, or at least documenting this as intentional (lazy initialization).


6. normalizeStageTagKeys has hardcoded alias mapping

File: server/utils/sessionIndex.js:23-26

if (value === 'presentation') return 'promotion';
if (value === 'research') return 'survey';

These aliases are undocumented and could be surprising. If these come from task stage values, consider mapping them in the task normalization layer (normalizeTask in auto-research.js) instead, so the tag system stays generic.


7. hydrateSessionRowsWithTags uses chunked queries with hardcoded chunk size

File: server/database/db.js:259

chunkSize = 900 — this is a reasonable SQLite parameter limit workaround, but worth a comment explaining why 900 (SQLite's default SQLITE_MAX_VARIABLE_NUMBER is 999).


8. projectsHaveChanges now includes geminiSessions — was this missing before?

File: src/hooks/useProjectsState.ts:97-101

Adding geminiSessions to the change detection is correct, but it's a bugfix unrelated to the tag feature. Consider calling it out in the PR description or splitting it into a separate commit for clarity.


9. session-tags-updated CustomEvent — global event bus pattern

Files: src/components/ResearchLab.jsx and src/hooks/useProjectsState.ts

Using window.dispatchEvent(new CustomEvent(...)) as a cross-component communication channel works but is fragile — no type safety, no guarantees about listener lifecycle, and hard to trace in debugging. This is fine for now, but consider migrating to a shared context or state management solution if more events follow this pattern.


10. sessionTagsById useEffect may overwrite optimistic updates

File: src/components/ResearchLab.jsx:1314-1320

useEffect(() => {
    const nextSessionTags = {};
    projectSessions.forEach((session) => { ... });
    setSessionTagsById(nextSessionTags);
}, [projectIdentity, projectSessions]);

This resets sessionTagsById whenever projectSessions changes, which could overwrite optimistic updates from handleToggleSessionStageTag. If a user toggles a tag and then projectSessions re-renders (e.g., from a WebSocket update), their local state could be clobbered. Consider merging rather than replacing.


11. Minor: SESSION_TAG_SOURCE_META has unused inferred entry

File: src/components/ResearchLab.jsx:1065

The inferred source type is defined in the metadata map but no code path currently produces tags with source: 'inferred'. If this is for future use, fine — but it's dead code for now.

…omments

- Fix parseTagRow to explicitly pick camelCase fields instead of spreading
  raw row (eliminates snake_case field leakage)
- Clean SessionTag type to camelCase-only, remove all tagKey||tag_key
  fallback patterns across 3 frontend files and 1 server file
- Extract duplicated stage tag JSX in SidebarSessionItem into shared variable
- Move stage alias mapping (presentation→promotion, research→survey) from
  normalizeStageTagKeys to normalizeTask in auto-research.js
- Fix sessionTagsById useEffect to merge instead of replace, preserving
  optimistic updates from tag toggling
- Add explanatory comments: sync fire-and-forget calls, dual-path tag
  application, lazy init, SQLite chunk size, CustomEvent TODO, reserved
  inferred source

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bbsngg
Copy link
Copy Markdown
Contributor Author

bbsngg commented Mar 23, 2026

Review fixes pushed: 4c5b99b

Changes

Core (Item 3): parseTagRow now explicitly picks camelCase fields instead of spreading raw row. SessionTag type cleaned to camelCase-only. Removed all 11 tagKey || tag_key fallback patterns across 4 files.

Code quality:

  • Item 4: Extracted duplicated stage tag JSX in SidebarSessionItem into a shared stageTagBadges variable
  • Item 6: Moved stage alias mapping (presentation→promotion, research→survey) from normalizeStageTagKeys to normalizeTask in auto-research.js
  • Item 10: Changed sessionTagsById useEffect to merge strategy — preserves optimistic updates from tag toggling instead of replacing wholesale

Comments (Items 1, 2, 5, 7, 9, 11): Added explanatory comments for sync fire-and-forget calls, dual-path tag application, lazy init, SQLite chunk size, CustomEvent TODO, and reserved inferred source.

Verification: npm run build ✅ | node --check ✅ | tsc --noEmit ✅ (only pre-existing vitest type issue)

@bbsngg bbsngg changed the title Add session stage tags and auto-linking feat: Add session stage tags and auto-linking Mar 23, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bbsngg bbsngg merged commit e285e72 into main Mar 26, 2026
1 check passed
@bbsngg bbsngg deleted the feat/session-stage-auto-tags branch March 26, 2026 06:17
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