Skip to content

fix(frontend): reset AttentionFeed showAll state bugs#366

Merged
dimakis merged 1 commit into
mainfrom
session/2026-06-02-a8942eaa8d1e
Jun 3, 2026
Merged

fix(frontend): reset AttentionFeed showAll state bugs#366
dimakis merged 1 commit into
mainfrom
session/2026-06-02-a8942eaa8d1e

Conversation

@dimakis
Copy link
Copy Markdown
Owner

@dimakis dimakis commented Jun 2, 2026

Summary

  • showAll sticks when items shrink: Added useEffect to auto-reset showAll when items.length drops to/below DEFAULT_VISIBLE_COUNT (5)
  • showAll persists across collapse/expand: toggleOpen now resets showAll to false on collapse, so reopening always shows the truncated view
  • Component tests: Added 13 tests covering show-more/less toggle, collapse reset, item-shrink reset, card rendering, navigation, summary/badge, and loading state

Follow-up to PR #354.

Test plan

  • All 13 new component tests pass (vitest run AttentionFeed.test.tsx)
  • Existing hook tests still pass (vitest run useAttentionFeed.test.ts)
  • Manual: expand "Show all", then trigger items to shrink — verify auto-collapse
  • Manual: expand "Show all", collapse section, reopen — verify truncated view

🤖 Generated with Claude Code

Copy link
Copy Markdown
Owner Author

@dimakis dimakis left a comment

Choose a reason for hiding this comment

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

Centaur Review

Found 6 issue(s) (2 warning).

server/chat.ts

Clean refactor from library-based to HTTP-based boot context with good test coverage and a well-tested AttentionFeed fix. Main concerns are minor: cosmetic type casts, a tokenBudget default change from 8000→0 in the zero-value fallback, and an untested default path.

  • 🔵 style (L383): The union type Record<string, unknown> | BootContextMessage in send() is immediately erased by data as Record<string, unknown>. BootContextMessage is already structurally assignable to Record<string, unknown>, so the union and cast are both unnecessary — the original signature works as-is. [fixable]
  • 🔵 style (L919): Double cast msg as unknown as Record<string, unknown> to store bootContext. Since BootContextMessage is assignable to Record<string, unknown>, a single cast (msg as Record<string, unknown>) suffices. The as unknown intermediate makes it look like there's a real type incompatibility when there isn't one. [fixable]
  • 🟡 regressions (L919): The cached bootContext now includes type: 'boot_context' (from BootContextMessage), whereas previously it was stored without type. Replay in ws-handler-v2.ts:285-288 does { type: 'boot_context', ...found.session.bootContext } — the spread overwrites the literal type with the same value so it works, but the redundancy is accidental. If BootContextMessage.type ever diverges from the literal, replay would silently send the wrong type. [fixable]
  • 🟡 regressions (L114): FALLBACK_BOOT_CONTEXT.tokenBudget is 0, while the old code always fell back to 8000 (via DEFAULT_TOKEN_BUDGET). This changes UI behavior when both ContexGin and the Python script fail — the boot context pill will show 0 budget instead of 8000. Minor since it only affects the double-failure case, but worth confirming this is intentional. [fixable]
  • 🔵 unsafe_assumptions (L130): localBootContextFallback uses dynamic import('child_process') and import('util') for modules that are already statically imported at the top of the file (execFileSync from child_process). The dynamic import adds unnecessary complexity — since child_process is a Node built-in that's already loaded, a static import would be simpler and more predictable. [fixable]

server/__tests__/boot-context.test.ts

Clean refactor from library-based to HTTP-based boot context with good test coverage and a well-tested AttentionFeed fix. Main concerns are minor: cosmetic type casts, a tokenBudget default change from 8000→0 in the zero-value fallback, and an untested default path.

  • 🔵 missing_tests (L178): The tokenBudget default of 8000 (line 199 of chat.ts: typeof boot.tokenBudget === 'number' ? boot.tokenBudget : 8000) is not tested. The 'handles empty sources array' test omits tokenBudget from the response but doesn't assert the resulting tokenBudget value. Adding expect(result.tokenBudget).toBe(8000) would cover this default. [fixable]

Comment thread server/chat.ts
/** Send data via transport (isOpen guard is inside the transport). */
function send(transport: SessionTransport, data: Record<string, unknown>) {
if (transport.isOpen()) transport.send(data);
function send(transport: SessionTransport, data: Record<string, unknown> | BootContextMessage) {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 style: The union type Record<string, unknown> | BootContextMessage in send() is immediately erased by data as Record<string, unknown>. BootContextMessage is already structurally assignable to Record<string, unknown>, so the union and cast are both unnecessary — the original signature works as-is. [fixable]

Comment thread server/chat.ts
if (s) s.bootContext = fallbackPayload;
}
})();
if (s) s.bootContext = msg as unknown as Record<string, unknown>;
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 style: Double cast msg as unknown as Record<string, unknown> to store bootContext. Since BootContextMessage is assignable to Record<string, unknown>, a single cast (msg as Record<string, unknown>) suffices. The as unknown intermediate makes it look like there's a real type incompatibility when there isn't one. [fixable]

Comment thread server/chat.ts
if (s) s.bootContext = fallbackPayload;
}
})();
if (s) s.bootContext = msg as unknown as Record<string, unknown>;
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 regressions: The cached bootContext now includes type: 'boot_context' (from BootContextMessage), whereas previously it was stored without type. Replay in ws-handler-v2.ts:285-288 does { type: 'boot_context', ...found.session.bootContext } — the spread overwrites the literal type with the same value so it works, but the redundancy is accidental. If BootContextMessage.type ever diverges from the literal, replay would silently send the wrong type. [fixable]

Comment thread server/chat.ts
source: 'local-fallback',
sourceCount: 0,
tokenCount: 0,
tokenBudget: 0,
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 regressions: FALLBACK_BOOT_CONTEXT.tokenBudget is 0, while the old code always fell back to 8000 (via DEFAULT_TOKEN_BUDGET). This changes UI behavior when both ContexGin and the Python script fail — the boot context pill will show 0 budget instead of 8000. Minor since it only affects the double-failure case, but worth confirming this is intentional. [fixable]

Comment thread server/chat.ts
const { execFile } = await import('child_process');
const { promisify } = await import('util');
const execFileAsync = promisify(execFile);
const { stdout } = await execFileAsync('python3', [scriptPath, '--json'], {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 unsafe_assumptions: localBootContextFallback uses dynamic import('child_process') and import('util') for modules that are already statically imported at the top of the file (execFileSync from child_process). The dynamic import adds unnecessary complexity — since child_process is a Node built-in that's already loaded, a static import would be simpler and more predictable. [fixable]

expect(result.fullMarkdown).toBeUndefined();
});

it('handles empty sources array', async () => {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 missing_tests: The tokenBudget default of 8000 (line 199 of chat.ts: typeof boot.tokenBudget === 'number' ? boot.tokenBudget : 8000) is not tested. The 'handles empty sources array' test omits tokenBudget from the response but doesn't assert the resulting tokenBudget value. Adding expect(result.tokenBudget).toBe(8000) would cover this default. [fixable]

AttentionFeed showAll state could stick true when items dropped below
the visible threshold or when the section was collapsed and reopened.
Add useEffect to auto-reset and reset on collapse toggle. Add 13
component-level tests covering toggle, collapse, shrink, and rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dimakis dimakis force-pushed the session/2026-06-02-a8942eaa8d1e branch from 97fc9a0 to 6d498be Compare June 3, 2026 15:44
Copy link
Copy Markdown
Owner Author

@dimakis dimakis left a comment

Choose a reason for hiding this comment

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

Centaur Review

Found 2 issue(s).

frontend/src/components/__tests__/AttentionFeed.test.tsx

Clean, well-scoped fix with solid test coverage. Two minor boundary cases in tests worth considering but nothing blocking.

  • 🔵 missing_tests (L148): The shrink test goes from 8 → 3 but doesn't test the boundary: items shrinking to exactly DEFAULT_VISIBLE_COUNT (5). Since the condition is <=, a test with 8 → 5 would verify the boundary is correct and guard against an off-by-one if someone changes it to <. [fixable]
  • 🔵 missing_tests (L148): No test for the case where items shrink but remain above threshold (e.g. 8 → 6 while showAll is true). In that scenario showAll stays true and the user sees all 6 items with a 'Show less' button — worth a test to document that this is intentional behavior, not an oversight. [fixable]

});

describe('showAll resets when items shrink', () => {
it('auto-resets showAll when items drop below threshold', () => {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 missing_tests: The shrink test goes from 8 → 3 but doesn't test the boundary: items shrinking to exactly DEFAULT_VISIBLE_COUNT (5). Since the condition is <=, a test with 8 → 5 would verify the boundary is correct and guard against an off-by-one if someone changes it to <. [fixable]

});

describe('showAll resets when items shrink', () => {
it('auto-resets showAll when items drop below threshold', () => {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 missing_tests: No test for the case where items shrink but remain above threshold (e.g. 8 → 6 while showAll is true). In that scenario showAll stays true and the user sees all 6 items with a 'Show less' button — worth a test to document that this is intentional behavior, not an oversight. [fixable]

@dimakis dimakis merged commit a2510a7 into main Jun 3, 2026
1 check 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