Skip to content

Plan: Fix sidebar route-app flicker on load + remove profile "Owner" label

URL: https://mkdocs.justinsforge.com/memory/plans/sidebar-flicker-and-owner-label-2026-07-05/

Date: 2026-07-05 Approved spec: System apps in the sidebar are server-rendered (layout apps prop) and stable; route apps (Business Manager, News, Social Analytics, Agent, Email, Clips) render only from the client hook useInstalledApps, which starts from an empty module cache, blanks to [] in its mount effect, then fetches /api/apps/install after hydration, so they flash in/out/in on load. Fix: server-compute the installed ids in the suite layout and pass them to the shell as initialInstalled; useInstalledApps seeds its shared cache on the very first load (cacheWs === null) so the first paint matches the authoritative list and the mount effect skips the blank+refetch. Separately, remove the · Owner label from the bottom profile row. Out of scope: - Workspace-switch flash (the deliberate anti-leak blank+refetch stays as-is) - App Library, owner-only filtering semantics, any /api/apps/install change - Any DB migration or write path

Task list

Task 1: Seed useInstalledApps from a server-provided list

  • Files: /home/justinwieb/forge-suite-wt/feat/sidebar-flicker-profile/components/apps/useInstalledApps.ts
  • What: Add an optional initial?: { ids: string[]; wsId: number | null } param. In the useState lazy initializer, when initial && cacheWs === null, set the module cache (installedIds = initial.ids; loaded = true; cacheWs = initial.wsId) so the first client paint matches the authoritative list. The existing effect then sees wsId === cacheWs and skips its blank+refetch; genuine workspace switches (cacheWs != null) are untouched.
  • Verification: cd <wt> && npx tsc --noEmit (typechecks clean); the hook still exports the same UseInstalledApps shape.
  • Commit: fix(apps): let useInstalledApps seed its cache from a server list

Task 2: Server-render installed ids and pass to the shell

  • Files: /home/justinwieb/forge-suite-wt/feat/sidebar-flicker-profile/app/(suite)/layout.tsx, /home/justinwieb/forge-suite-wt/feat/sidebar-flicker-profile/components/workspace/WorkspaceShell.tsx
  • What: In the layout, await getInstalledApps(me, wsId) (route apps) and combine with the already-computed appNodes types (installed system apps) into initialInstalled, pass it to WorkspaceShell. In the shell, accept initialInstalled?: string[] and call useInstalledApps({ ids: initialInstalled ?? [], wsId: ws.id }).
  • Verification: cd <wt> && npx tsc --noEmit; then browser: hard-reload http://100.97.43.104:3061 several times and confirm Business Manager / News / Social Analytics / Agent no longer flash in/out.
  • Commit: fix(sidebar): server-render installed apps so route apps don't flash on load

Task 3: Remove the "Owner" label from the profile row

  • Files: /home/justinwieb/forge-suite-wt/feat/sidebar-flicker-profile/components/workspace/AccountMenu.tsx
  • What: In the account row, render only {shownName} (drop the inner <span className="ws-acct-plan"> · {shownPlan}</span>). Remove the now-unused plan prop and the shownPlan computation.
  • Verification: cd <wt> && npx tsc --noEmit && npx eslint components/workspace/AccountMenu.tsx; browser: the bottom profile row shows the name with no · Owner.
  • Commit: feat(sidebar): drop the Owner/Member label from the profile row

Task 4: Verify end-to-end in the running app

  • Files: none (verification only)
  • What: With the dev server on :3061, load the workspace, hard-reload 5+ times watching the Apps section, and confirm (a) no route-app flash, (b) system apps still stable, (c) profile row shows name only. Capture a screenshot to the scratchpad as evidence.
  • Verification: screenshot saved + visual confirmation; git log --oneline shows three separate commits.
  • Commit: none (no code change) — report branch + review notes to Justin.