Skip to content

fix: stabilize viewer navigation and timeline layout#313

Merged
rohitg00 merged 2 commits into
rohitg00:mainfrom
flamerged:fix/viewer-navigation-ui
May 17, 2026
Merged

fix: stabilize viewer navigation and timeline layout#313
rohitg00 merged 2 commits into
rohitg00:mainfrom
flamerged:fix/viewer-navigation-ui

Conversation

@flamerged
Copy link
Copy Markdown
Contributor

@flamerged flamerged commented May 12, 2026

Summary

  • Keep the viewer header, tabs, and feature flag banner from shrinking or overlapping page content.
  • Add hash routes for viewer tabs and make the top-left brand link back to the dashboard.
  • Constrain timeline observation cards so long tool names, JSON payloads, and file paths wrap or truncate inside their parent card.
  • Add an AM favicon and allow data-image favicons in the viewer CSP.
  • Keep feature-flag dismissals page-local so warnings can be closed until refresh without persisting stale localStorage state.

Root Cause

The viewer is a single HTML/CSS/JS surface, and several fixed UI rows were normal flex children that could shrink under tall view content. Timeline cards also used an unconstrained flex header and preformatted payloads, so long tool names and JSON strings could overflow their card and push the metadata controls out of place.

Validation

  • node --check on the extracted viewer script
  • git diff --check -- src/viewer/index.html src/auth.ts test/viewer-security.test.ts
  • ../agentmemory/node_modules/.bin/vitest run test/viewer-security.test.ts
  • npm run build from the worktree using the existing local node_modules install
  • Manual preview through the real viewer proxy on a separate local port

Follow-up

The viewer remains hard to develop locally because the frontend is tightly coupled to the Node viewer proxy and REST port assumptions. This PR keeps the fix scoped, but a cleaner frontend/backend dev split would make these UI changes much easier to test.

Summary by CodeRabbit

  • New Features

    • Route-based tab navigation using URL history for improved navigation experience
    • Added favicon to viewer
    • Feature-flag dismissals now update immediately (dismissals held in-memory)
  • Style

    • Enhanced UI styling for headers, tabs, and timeline observation cards
    • Redesigned feature-flag banner presentation and layout
    • Improved text wrapping and truncation in observation cards
  • Security

    • Content Security Policy updated to allow image loading from data: URLs
  • Tests

    • Updated security tests to verify the new image loading policy

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

@flamerged is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b136239b-f376-4443-866a-2db46fea695e

📥 Commits

Reviewing files that changed from the base of the PR and between 56ab412 and 0f68f93.

📒 Files selected for processing (1)
  • src/viewer/index.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/viewer/index.html

📝 Walkthrough

Walkthrough

Adds data: to viewer CSP img-src, implements hash-driven tab routing with URL/history sync, refactors header/observation card layout and CSS, and migrates feature-flag dismissal to an in-memory state with updated banner markup and styling.

Changes

Viewer Routing, UI Refinements, and Feature Flags

Layer / File(s) Summary
CSP Image Loading Security
src/auth.ts, test/viewer-security.test.ts
CSP img-src directive updated to allow data: URLs in addition to 'self'; security test verifies the directive.
Header, favicon, and basic UI styling
src/viewer/index.html
Adds favicon, makes header brand a linked route target, updates brand hover/focus styling, adjusts tab-bar z-index/flex, and prevents timeline-item overflow.
Observation card layout and long-text handling
src/viewer/index.html
Refactors .obs-card to grid for title/meta rows, constrains card widths/overflow, truncates long titles/tags, and improves .obs-narrative/pre wrapping rules.
Feature-flag banners and dismissal UI
src/viewer/index.html
Restyles .flag-banners, updates banner/button/close markup and CSS, and embeds escaped dismissal keys in data-* attributes for the new dismissal flow.
Tab routing, route helpers, and in-memory flag state
src/viewer/index.html
Introduces TAB_IDS, normalizeTab, tabFromRoute, updateTabRoute, state.flagsDismissed, and upgrades switchTab(tab, opts) to normalize/validate tabs, optionally update URL history, set aria-current, re-render banners, wire click handlers, sync on hashchange/popstate, and initialize active tab from the route.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through hashes, tabs in tow,
Favicon shining, cards in a row,
Flags now fleeting, kept in RAM,
Images sip data: like a charm,
The viewer hums — hop, tap, and go!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: stabilize viewer navigation and timeline layout' directly corresponds to the main objectives: stabilizing navigation (hash routes, tab routing) and timeline layout (constraining observation cards). However, it omits significant changes like CSP updates for data images and feature-flag dismissal logic.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@flamerged flamerged marked this pull request as ready for review May 12, 2026 22:48
@flamerged flamerged force-pushed the fix/viewer-navigation-ui branch from 22df145 to 56ab412 Compare May 13, 2026 13:58
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: 1

🧹 Nitpick comments (2)
src/viewer/index.html (2)

1178-1183: 💤 Low value

Optional: set aria-current on the active tab for screen-reader navigation.

switchTab toggles the visual .active class on the matching [data-tab] button but does not surface that state to assistive tech. Reflecting it via aria-current="page" (or aria-selected if you want true tablist semantics) lets screen-reader users perceive the current tab.

♻️ Proposed refactor
       document.querySelectorAll('.tab-bar button').forEach(function(b) {
-        b.classList.toggle('active', b.dataset.tab === tab);
+        var isActive = b.dataset.tab === tab;
+        b.classList.toggle('active', isActive);
+        if (isActive) b.setAttribute('aria-current', 'page');
+        else b.removeAttribute('aria-current');
       });

Also applies to: 948-961

🤖 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 `@src/viewer/index.html` around lines 1178 - 1183, The tab-switching code
currently only toggles the visual .active class; update the logic in the
function that contains the shown selectors (the switchTab handler that queries
'.tab-bar button' and '.view') to also set aria-current="page" on the active
button and remove that attribute from all other buttons (or use
aria-selected="true"/"false" if you adopt full tablist semantics); ensure you
set aria-current only when b.dataset.tab === tab and explicitly remove the
attribute for non-active buttons so screen readers receive the current-tab
state.

3418-3423: ⚡ Quick win

Preserve modifier/middle-click new-tab behavior on [data-tab-link].

The brand link is now a real <a href="#dashboard">, but the handler always calls e.preventDefault(). That suppresses Ctrl/Cmd/middle-click "open in new tab" semantics — clicking with a modifier will just toggle the dashboard tab in the current view instead of opening a new tab at #dashboard. Letting modified clicks pass through preserves expected anchor behavior while keeping plain clicks SPA-style.

♻️ Proposed refactor
     document.querySelectorAll('[data-tab-link]').forEach(function(link) {
       link.addEventListener('click', function(e) {
+        // Let users open the link in a new tab/window with modifier or middle-click.
+        if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
         e.preventDefault();
         switchTab(link.getAttribute('data-tab-link'));
       });
     });
🤖 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 `@src/viewer/index.html` around lines 3418 - 3423, The click handler on
elements selected by document.querySelectorAll('[data-tab-link]') always calls
e.preventDefault(), which breaks modifier/middle-click behavior; update the
handler so it only prevents default and runs
switchTab(link.getAttribute('data-tab-link')) for unmodified left-clicks.
Concretely, detect modified clicks (e.metaKey || e.ctrlKey || e.shiftKey ||
e.altKey) and middle/right buttons (e.button !== 0 or e.button === 1 for middle)
and early-return without calling e.preventDefault(), otherwise call
e.preventDefault() and switchTab as before.
🤖 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.

Inline comments:
In `@src/viewer/index.html`:
- Around line 2411-2421: The obs-title span currently renders the truncated
title text without a tooltip; update the HTML generation where the span is
created (the line producing '<span class="obs-title">' + esc(title) + '</span>')
to include a title attribute set to the full escaped title (e.g., '<span
class="obs-title" title="' + esc(title) + '">' + esc(title) + '</span>'); apply
the same change to the other occurrence of the obs-title span around lines
474-483 so long titles show the full text on hover while preserving the existing
ellipsis styling.

---

Nitpick comments:
In `@src/viewer/index.html`:
- Around line 1178-1183: The tab-switching code currently only toggles the
visual .active class; update the logic in the function that contains the shown
selectors (the switchTab handler that queries '.tab-bar button' and '.view') to
also set aria-current="page" on the active button and remove that attribute from
all other buttons (or use aria-selected="true"/"false" if you adopt full tablist
semantics); ensure you set aria-current only when b.dataset.tab === tab and
explicitly remove the attribute for non-active buttons so screen readers receive
the current-tab state.
- Around line 3418-3423: The click handler on elements selected by
document.querySelectorAll('[data-tab-link]') always calls e.preventDefault(),
which breaks modifier/middle-click behavior; update the handler so it only
prevents default and runs switchTab(link.getAttribute('data-tab-link')) for
unmodified left-clicks. Concretely, detect modified clicks (e.metaKey ||
e.ctrlKey || e.shiftKey || e.altKey) and middle/right buttons (e.button !== 0 or
e.button === 1 for middle) and early-return without calling e.preventDefault(),
otherwise call e.preventDefault() and switchTab as before.
🪄 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

Run ID: 4e007d40-a422-4b6d-8917-b421f42eb147

📥 Commits

Reviewing files that changed from the base of the PR and between 22df145 and 56ab412.

📒 Files selected for processing (3)
  • src/auth.ts
  • src/viewer/index.html
  • test/viewer-security.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/viewer-security.test.ts
  • src/auth.ts

Comment thread src/viewer/index.html
@rohitg00 rohitg00 merged commit 63a0742 into rohitg00:main May 17, 2026
1 of 2 checks passed
rohitg00 added a commit that referenced this pull request May 17, 2026
The viewer-session-id sandbox (added in #366) ran fine until #313
landed hash-routing in `switchTab` — `updateTabRoute` now calls
`history.replaceState`, and the aria-selected toggle calls
`removeAttribute` on the non-active tab buttons. Neither was stubbed
in the vm sandbox, so the combined main produced 2 failures:

  TypeError: b.removeAttribute is not a function
  ReferenceError: history is not defined

Both PRs (#366, #313) passed CI in isolation. The combined failure
only surfaced after merge. Stub both globals.

987/987 tests now pass on main.
rohitg00 added a commit that referenced this pull request May 17, 2026
…#452)

#313 added `data:` to the viewer img-src so an inline-SVG favicon
encoded as `data:image/svg+xml,...` could load. Bare `data:` allows
arbitrary data-URI image types (data:image/png;base64,..., etc),
which the viewer does not need.

Self-host the favicon at `/favicon.svg` and revert the CSP to
`img-src 'self'`. Same artwork (dark rounded tile + green "AM"),
served from a real URL with `Content-Type: image/svg+xml` and
`Cache-Control: public, max-age=3600`.

- new file: src/viewer/favicon.svg (extracted from the URL-decoded
  inline blob)
- src/auth.ts: img-src reverted to 'self'
- src/viewer/index.html: favicon link now href="/favicon.svg"
- src/viewer/server.ts: new GET /favicon.svg route, candidate-path
  loader mirrors loadViewerTemplate() so it resolves both from
  source (vitest) and from dist/ (npm run start)
- package.json: build script copies favicon.svg into dist/viewer/
  alongside index.html
- test/viewer-security.test.ts: CSP assertion now requires bare
  `img-src 'self'` and rejects any `data:` allowance; new e2e test
  hits /favicon.svg and verifies SVG payload + headers
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