Skip to content

fix(web): port homepage UI tweaks from platform #7421 (LUM-1726)#31321

Merged
ashleeradka merged 4 commits into
mainfrom
claude/fix-home-placeholder-L7LcW
May 20, 2026
Merged

fix(web): port homepage UI tweaks from platform #7421 (LUM-1726)#31321
ashleeradka merged 4 commits into
mainfrom
claude/fix-home-placeholder-L7LcW

Conversation

@ashleeradka
Copy link
Copy Markdown
Contributor

Summary

Drift port of vellum-assistant-platform#7421 (commit f216413, Jason, May 20). Brings the homepage UI in this repo back in sync with the platform repo.

Tracking: LUM-1726

Investigation note

The original report was "home shows an empty placeholder instead of the rich UI that's live on dev." After diffing the two repos, the home components (home-page, home-greeting-header, home-suggestion-pill-bar, home-feed-list, home-recap-row, home-feed-filter-bar, home-detail-panel, hooks, types, API) are all fully ported and wired up. The "Here's what's been going on" + "No items to show" the user saw locally is the normal empty state — the sidebar in that same screenshot showed zero conversations across all categories, indicating an empty local backend rather than a porting gap.

What I did find was that platform PR #7421 merged the same day with 6 homepage polish fixes that had not been ported. This PR ports those.

Changes (mirror of platform commit f216413)

  • Suggestion pill icon size: size-[9px]size-[18px]
  • Hide feed filter bar when only one category is present (length === 0length <= 1)
  • Filter bar → feed sections spacing: app-spacing-lgapp-spacing-sm
  • Unread indicator dot: -right-0.5-left-0.5 (top-left of feed row icon)
  • Detail panel default: defaultLeftWidth={600}defaultLeftPercent={50}
  • Derive effectiveFilter from activeFilter + presentCategories so a stale category filter falls back to "All" instead of stranding the feed in an empty state with no visible filter controls

Adds new defaultLeftPercent prop to ResizablePanel (design library) with useLayoutEffect-based resolution that respects existing localStorage preferences. Resolves before paint to avoid a pixel-fallback flash.

Test plan

  • bun run lint — clean (1 unrelated pre-existing warning in lib/errors/report.ts)
  • bunx tsc --noEmit — clean
  • Manual: verify suggestion pill icons render at 18px on /assistant/home
  • Manual: verify filter bar hides when only one category is present
  • Manual: verify unread dot renders on top-left of feed row icon
  • Manual: verify detail panel opens 50/50 by default (or respects stored width if previously dragged)
  • Manual: trigger stale filter → category disappears → filter resets to "All" instead of empty state

https://claude.ai/code/session_013dBXRbLF218UhdLq7FEAvv


Generated by Claude Code

Drift port of vellum-assistant-platform#7421 (commit f216413).

- Suggestion pill icon size: 9px → 18px
- Hide feed filter bar when only one category is present (<=1)
- Filter bar → feed sections spacing: lg → sm (8px)
- Unread indicator dot: right → left of feed row icon
- Detail panel default: defaultLeftWidth={600} → defaultLeftPercent={50}
  resolved via useLayoutEffect to prevent flash from pixel fallback
- Derive effectiveFilter from activeFilter + presentCategories so a
  stale category filter automatically falls back to "All" instead of
  stranding the feed in an empty state with no visible filter controls

Adds defaultLeftPercent prop to ResizablePanel (design library) with
useLayoutEffect-based resolution that respects existing localStorage
preferences.

https://claude.ai/code/session_013dBXRbLF218UhdLq7FEAvv
@linear
Copy link
Copy Markdown

linear Bot commented May 20, 2026

LUM-1726

@ashleeradka ashleeradka marked this pull request as ready for review May 20, 2026 18:53
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ccd1c97383

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +138 to +139
const stored = localStorage.getItem(storageKey);
if (stored != null) return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore malformed persisted widths before skipping percent default

The new defaultLeftPercent path treats any existing localStorage entry as a valid preference and returns early, even when that value is malformed (for example "NaN" or non-numeric data). In that case the useState initializer falls back to defaultLeftWidth, but the layout effect never applies the percentage default, so users can get a persistent, unintended width. Parse and validate the stored value here (same as initialization) before deciding to skip percentage resolution.

Useful? React with 👍 / 👎.

vex-assistant-bot[bot]
vex-assistant-bot Bot previously approved these changes May 20, 2026
Copy link
Copy Markdown
Contributor

@vex-assistant-bot vex-assistant-bot Bot left a comment

Choose a reason for hiding this comment

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

APPROVE

What this does: Drift port of platform PR #7421 (commit f216413, Jason, May 20) — 6 homepage polish fixes that hadn't been brought over. Single commit, no logic gap from the platform.


All 6 platform changes verified:

  • Suggestion pill icon size-[9px]size-[18px] — 9px was rendering invisible on most screens ✅
  • Filter bar hidden when length <= 1 — no point showing a filter with a single option ✅
  • Filter bar → feed sections gap app-spacing-lgapp-spacing-sm
  • Unread dot -right-0.5-left-0.5 — top-left of feed row icon ✅
  • defaultLeftWidth={600}defaultLeftPercent={50} — responsive 50/50 split instead of fixed px ✅
  • effectiveFilter stale-filter fallback — if activeFilter names a category that no longer exists in presentCategories, falls back to null and shows all items rather than stranding the feed empty ✅

useLayoutEffect in ResizablePanel — correct choice:
The new effect runs before paint to resolve a percentage-based default width, avoiding a single-frame flash of the pixel fallback. The anti-patterns KB explicitly mandates useLayoutEffect over useEffect for "set state before paint" patterns (evidence: PR #31144). The existing useEffect at line 118 (resize listener) doesn't need before-paint timing, so the two effects are correctly using different variants. Dep array [defaultLeftPercent, storageKey, clamp] is stable for static props — runs once on mount. ✅

clamp stability: Defined via useCallback([minLeftWidth, minRightWidth]) — stable for the fixed props passed at the call site. ✅

effectiveFilter derivation: Primitive result (string | null), computed fresh each render from activeFilter state + presentCategories. useMemo isn't needed since the value is a primitive and the compute is trivial — matches the platform implementation. ✅

Empty state message uses effectiveFilter: The "No items match the selected filter" branch correctly uses effectiveFilter, so a stale filter being cleared to null won't display the wrong empty state copy. ✅


No anti-patterns detected. No bot reviews yet — triggers Devin + Codex second approval.

Reviewed at ccd1c973.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +39 to +42
const effectiveFilter =
activeFilter && presentCategories.includes(activeFilter)
? activeFilter
: null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Stale activeFilter state can silently re-activate when categories reappear

The effectiveFilter derived state at apps/web/src/domains/home/home-feed-list.tsx:39-42 gracefully falls back to null when the selected category disappears from presentCategories. However, the underlying activeFilter React state is never reset — it retains the old category. If that category later reappears (e.g., new items arrive), effectiveFilter will snap back to the previously selected filter without user interaction. This is an edge case (requires dismissing all items of a category, then new items arriving in the same category), and could be seen as a feature ("sticky filter memory"), but it may surprise users. A useEffect that calls setActiveFilter(null) when activeFilter is not in presentCategories would make the reset explicit.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Two findings flagged in review, both fixed properly rather than
faithful-port mirroring of platform — this repo doesn't carry forward
upstream tech debt.

1. ResizablePanel (Codex P2): malformed localStorage values made the
   useState initializer fall back to defaultLeftWidth while the new
   useLayoutEffect skipped percent resolution, leaving a persistent
   unintended width. Extracted a readStoredWidth() helper that does
   shape + finiteness validation in one place and is used by both the
   initializer and the percent-resolution effect.

2. HomeFeedList (Devin): the previous diff resolved a stale activeFilter
   via the derived effectiveFilter for rendering, but never reset the
   underlying state. If the selected category disappeared and later
   reappeared, the filter would silently re-activate. Use React's
   adjust-state-during-render pattern to keep activeFilter in sync with
   effectiveFilter — React bails out on same-value setState so this
   avoids a synchronization Effect.
   https://react.dev/reference/react/useState#storing-information-from-previous-renders

https://claude.ai/code/session_013dBXRbLF218UhdLq7FEAvv
claude added 2 commits May 20, 2026 19:05
Add explicit guidance to apps/web/AGENTS.md so this rule persists across
agent sessions and to anyone (human or AI) working on the migration:

- A faithful port preserves behavior and feature parity, not every
  implementation choice. The stack changed; the code should change with
  it.
- This is an OSS repo — align with React, React Router, and major OSS
  players' recommended patterns rather than mirroring the platform repo.
- When a bot review flags a real issue in newly-ported code, fix it
  rather than dismissing as "matches upstream." The platform will be
  deprecated; we don't need to mirror its bugs.
- Small refactors belong in the port PR; large ones get a separate
  Linear issue.
- PR descriptions should call out divergences from the platform
  implementation so reviewers understand the deltas.

https://claude.ai/code/session_013dBXRbLF218UhdLq7FEAvv
OSS contributors working in this repo have no context on the platform
repo and shouldn't need to. The "don't carry over upstream tech debt"
policy belongs in the platform repo's web docs (alongside the existing
migration content), not here. Reverting the section added in 845ebf0.

Equivalent guidance moves to vellum-assistant-platform/web/CLAUDE.md.

https://claude.ai/code/session_013dBXRbLF218UhdLq7FEAvv
@ashleeradka ashleeradka merged commit d58547f into main May 20, 2026
20 checks passed
@ashleeradka ashleeradka deleted the claude/fix-home-placeholder-L7LcW branch May 20, 2026 19:15
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