Skip to main content

Documentation Index

Fetch the complete documentation index at: https://grantmaster.dev/llms.txt

Use this file to discover all available pages before exploring further.

GrantMaster Global UI Refactor Notes

Phase 0 Inventory

Canonical shared surfaces

  • App shell: src/Layout.tsx
  • Main chrome: src/components/layout/Header.tsx, src/components/layout/Sidebar.tsx
  • Canonical page wrappers: src/components/layout/PageLayout.tsx, src/components/layout/PageHeader.tsx, src/components/layout/PageShell.tsx
  • Canonical page tabs: src/components/ui/PageTabs.tsx
  • Core cards: src/components/ui/card.tsx, src/components/widgets/infowidgets/KpiCard.tsx
  • Widget/dashboard wrappers: src/components/widgets/WidgetContainer.tsx, src/components/widgets/WidgetDashboardBase.tsx
  • Page templates: src/components/page-templates/BentoDashboardTemplate.tsx, src/components/page-templates/RegistryPageTemplate.tsx, src/components/page-templates/FeedPageTemplate.tsx
  • Overlay primitives: src/components/ui/modal-system/AppModal.tsx, src/components/ui/modal-system/AppDrawer.tsx, src/components/modals/ModalRenderer.tsx
  • Table/list primitives: src/components/ui/data-table/DataTable.tsx, src/components/ui/SearchFilterBar.tsx
  • Empty/data states: src/components/ui/EmptyState.tsx, src/components/layout/PageStatePanel.tsx, src/components/ui/DataStateWrapper.tsx

Duplication and drift

  • PageLayout exists in both src/components/layout/PageLayout.tsx and src/components/ui/PageLayout.tsx. The ui export is a thin wrapper, but max-width and padding mappings are duplicated.
  • PageHeader exists in both src/components/layout/PageHeader.tsx and src/components/ui/PageHeader.tsx. The ui file is only a re-export, but the split still creates ambiguity.
  • Card styling is fragmented across ui/card.tsx, ui/CardVariants.tsx, widgets/infowidgets/KpiCard.tsx, ui/widget-system.tsx, and many feature-local wrappers.
  • Empty states are fragmented across ui/EmptyState.tsx, PageStatePanel.tsx, DataStateWrapper.tsx, widget empty states, and feature-local placeholders.
  • List/table wrappers are split between RegistryPageTemplate, FeedPageTemplate, SearchFilterBar, DataTable, and many feature-local toolbars.
  • Modal patterns are split between AppModal, AppDrawer, raw DialogContent, raw SheetContent, and several legacy custom modal shells.

Repeated one-off layout patterns

  • Repeated space-y-6, gap-6, p-5, p-6, p-8, max-w-*, min-w-*, and hardcoded modal/table widths across src/features and src/components.
  • Many dashboard and people pages use local KPI wrappers instead of the shared KPI surface.
  • Several feature pages still compose PageLayout + PageTabs + conditional rendering manually instead of using PageShell.

Rendering hotspots

  • Many tabbed pages still use activeTab === ... && <Component />, which remounts tab content on every switch.
  • DashboardTabs.tsx intentionally hydrates visited tabs, which is a good direction and should become the standard for heavy overview surfaces.
  • Layout.tsx owns sidebar width, collapse state, assistant state, mobile menu state, and shell rendering in one component, so page-local route changes still pass through a large shell.
  • PageShell.tsx recreates action nodes and tab arrays on every render. Not critical by itself, but it sits on a common path.
  • WidgetDashboardBase.tsx already lazy-loads widgets and delays hydration, so later phases should align chart-heavy pages with that pattern instead of re-solving it page by page.
  • DataTable.tsx already supports virtualization, but many feature tables still use custom table markup and bypass that path entirely.

Shared map for phased replacement

  • Shared layout components:
    • Layout
    • PageLayout
    • PageHeader
    • PageShell
    • PageTabs
  • Page shell components:
    • PageLayout
    • PageHeader
    • PageStatePanel
    • SearchFilterBar
  • Dashboard/widget primitives:
    • WidgetContainer
    • WidgetDashboardBase
    • KpiCard
    • MetricCard
    • SurfaceMetricGrid
    • SurfaceSection
  • Modal primitives:
    • AppModal
    • AppDrawer
    • ModalRenderer
  • Table/list wrappers:
    • DataTable
    • RegistryPageTemplate
    • FeedPageTemplate
    • SearchFilterBar
  • Chart wrappers:
    • Widget dashboard surfaces under src/components/widgets
    • Chart-heavy overview tabs in src/features/dashboard, src/features/grants, and src/features/users

Batch Plan

Phase 1

  • Normalize shared layout and spacing tokens in src/index.css
  • Introduce canonical page shell primitives for container, section, grids, KPI rows, and split layouts
  • Introduce a unified card system with KPI, data, action, and empty-state variants
  • Replace the existing decorative empty-state primitive with a stable shared empty-state surface while keeping backwards compatibility for current callers
  • Update canonical wrappers first: PageLayout, PageHeader, PageTabs, ui/card, EmptyState

Phase 2+

  • Move route-level tab pages from manual PageLayout + PageTabs composition toward PageShell
  • Convert overview/dashboard families to the shared grid and KPI row primitives before touching denser data modules
  • Migrate data-heavy modules onto shared table/filter wrappers instead of adding local toolbar/table shells
  • Collapse remaining raw DialogContent and SheetContent usage into the shared modal and drawer primitives
  • Standardize keep-mounted tab strategies for heavy dashboards, charts, and editors where remount costs are visible

Phase 4 Notes

  • BentoDashboardTemplate is now the canonical overview/dashboard composition layer for KPI rows and 12-column analytics rows.
  • Overview widgets should prefer shared widget empty states over zero-value shells so sparse data still reads as intentional.
  • SurfaceSection is the shared executive dashboard card wrapper; row height, side-panel density, and title/meta spacing should be solved there first.
  • Quick actions belong inside a subdued side-panel card, not as a competing primary surface.

Phase 5 Notes

  • RegistryPageTemplate and FeedPageTemplate are now the canonical Phase 5 seams for data-dense module cleanup. Shared toolbar, selection, and empty-state decisions should land there before touching individual tabs.
  • SurfaceToolbar is the standard filter and result-count wrapper for list, feed, and registry views. Page-level actions stay in the page header; list-local actions belong in the toolbar.
  • SurfaceSelectionBar is the standard sticky batch-action surface for list and table selection. Batch-selection styling should no longer be rebuilt per module.
  • Data-heavy wrappers should render inside DataCard surfaces so table shells, feed shells, and no-data/loading states inherit the same spacing, radius, and border language.
  • Later Phase 5 follow-ups should target row-density behavior, long-list virtualization adoption, and eventual migration of custom registry tables toward the shared DataTable path where it is safe.

Phase 6 Notes

  • AppModal and AppDrawer now share the same close-behavior contract: non-dismissible overlays block escape, pointer-dismiss, drag-dismiss, and the inline close button.
  • AppDrawer now owns a real drawer sizing system (sm/md/lg/xl/full) instead of forcing width through local SheetContent classes.
  • DrawerFooter is now the canonical drawer action row and intentionally mirrors ModalFooter so button placement stays consistent between dialogs and drawers.
  • Shared form primitives (FormSection, FormField, FormActions, FormGrid) now carry the tokenized spacing and text rhythm for overlay forms. Local modal forms should lean on those instead of re-specifying label/help/error spacing.
  • ConfigDrawerTemplate is the canonical settings/configuration overlay wrapper for field-heavy drawers.
  • ApprovalDomainForm and TemplateEditorModal were moved onto the shared drawer/modal system as Phase 6 reference migrations. Additional raw DialogContent and SheetContent usages should be collapsed into this path incrementally in later batches.

Phase 7 Notes

  • SurfaceMetricSummaryRow is now the standard framing layer for analytical cards that need supporting metrics above charts. Avoid dropping isolated values or percentages into chart cards without that summary context.
  • WidgetDonut and WidgetSparkline now lazy-load their recharts payloads behind stable placeholders so heavy chart code is deferred until those widgets actually mount.
  • Shared chart widgets must preserve stable minimum heights in both empty and loaded states to avoid card jump and legend drift.
  • WidgetDonut now treats input data as immutable; legend ranking should always sort a copied array instead of mutating caller-provided data.
  • The representative analytics tabs in Grants Tracker, Post-Award Financials, and Applications Overview now use the summary-row + chart structure that later analytics pages should follow.

Phase 8 Notes

  • Module-local wrappers should collapse toward shared shell and surface primitives before page-level redesign work. This batch moved Settings, Support, Documents, Compliance Policies, and Grantors further onto that path without touching business logic.
  • SettingsPageHeader now rides on the shared card/header system, so settings pages inherit the same spacing, radius, and typography rhythm as the rest of the app instead of carrying a bespoke banner treatment.
  • Support sub-tabs should not nest PageLayout inside SupportPage. Local subpage intros now use SurfacePageHeader, and content cards should use shared DataCard, SurfaceToolbar, SurfaceSection, and SurfaceEmptyState primitives.
  • CoverageTab and SmartSearchTab are the reference pattern for routed sub-tabs that need local framing inside an already-mounted page shell: use a local intro card plus shared analytical sections, not another page shell.
  • Grantors should keep using the module-local helpers, but those helpers now need to delegate to the shared surfaces (SurfaceInsightList, DataCard, SurfaceSection, SurfaceToolbar) instead of rebuilding near-duplicate cards and table wrappers.
  • DocumentBrowser empty states now flow through the shared EmptyState primitive; future document module cleanup should continue removing local empty/list placeholders in favor of the shared state surfaces.

Phase 9 Notes

  • Default page content padding now compresses responsively (p-4 -> p-5 -> p-6) so smaller desktop widths keep more usable horizontal space without changing page structure.
  • PageHeader action areas now prefer wrapping over horizontal scrolling, which keeps title/action rows readable when local action sets grow on narrower desktops.
  • PageTabs now reserves side padding for scroll affordances only when the tab rail actually overflows. Non-overflowing tab bars no longer waste width on smaller desktop screens.
  • SearchFilterBar and the toolbar surfaces that depend on it now stay stacked until xl, with the search field allowed to shrink fully. Filters and actions wrap instead of forcing cramped inline layouts at mid-desktop widths.
  • BentoDashboardTemplate now keeps KPI rows and filter capsules stacked until xl, and the 12-column dashboard rows also wait until xl before splitting. This avoids thin, panic-width widgets on small desktop windows.
  • PageGrid 12-column layouts now collapse to a single column below lg, and registry tables now use a consistent minimum width inside controlled horizontal scroll containers instead of collapsing headers and cells into unreadable widths.

Phase 10 Notes

  • KeepAliveTabPanels is now the shared visited-tab primitive for route-driven tab pages that should preserve mounted state after first visit. Use it when remounting tabs causes noticeable panel churn or lazy-loading flicker.
  • Workflow, Documents, and Policies route tabs now keep visited panels mounted instead of remounting the subtree on every tab switch.
  • PageTabs no longer uses extra mount-time state to discover the tablist id or right-fade visibility; those values are now derived so the shared tab shell commits less during initial page mount.
  • PageShell now memoizes mapped actions, breadcrumbs, and tab models so route-driven shell pages avoid rebuilding common chrome inputs unless the resolved config actually changes.
  • The route perf budget regression for workflow tab switches now passes again, so the shared tab-shell path is back within the expected commit envelope.

Phase 11 Notes

  • Shared surface depth is intentionally calmer now: --shadow-card and --shadow-card-hover were reduced, and the canonical cardSurfaceClassName no longer swaps to a heavier dark-mode-only shadow. New surfaces should not reintroduce louder one-off shadow stacks.
  • Shared action sizing now runs through the --action-height-* tokens in Button, including icon buttons. Page actions, toolbar actions, and overflow triggers should inherit those sizes instead of hardcoding local heights.
  • Badge now has a standardized minimum height, line-height, icon sizing, and text rhythm so count pills and metadata chips align consistently across cards, tables, and headers.
  • SurfaceEmptyState no longer nests a Card around EmptyStateCard, which removes a duplicated border/shadow layer from no-data views. Future empty states should avoid wrapping the shared empty-state primitive in another card unless there is a genuine compositional reason.
  • SurfacePageHeader, SurfaceNotice, SurfaceActionBar, and metric summary surfaces now lean on the same title, radius, and padding tokens as the rest of the app. Final polish work should continue collapsing executive surfaces toward those primitives instead of reintroducing bespoke hero/header treatments.