chore(canary): white-canvas frontend design layer pre-operator UI

Strip the inherited template aesthetic so the next AI/designer starts
from a true blank canvas. AI tunnel-vision pattern: when there's an
existing layout/sidebar/color palette, the model tends to redesign
in place rather than truly start fresh. Removing the visual scaffold
forces fresh-canvas thinking when operator-driven UI work begins.

Removed:
- pages/dashboard, pages/settings — template "Welcome / Available
  Stores / Suggested Features" stubs that have no canary purpose.
- pages/landing/landing.module.scss, core/app/{shell,toast}.module.scss
  — empty header stubs imported only for side effects.
- core/lib/shell.ui.store.ts — zustand store for sidebar open/
  collapsed/theme state; entirely template-coupled. core/lib/index.ts
  reduced to an empty barrel ready for canary stores.
- core/app/shell.tsx sidebar+nav+header layout; replaced with bare
  ErrorBoundary > Suspense > Outlet. Keeps robustness pattern,
  drops the navbar template a designer would otherwise inherit.
- App.tsx hardcoded dark Toaster theme + wrapping .app div.
- styles/_tokens.scss color palette ($bg-page, $text-default, etc.)
  — biased toward a specific dark aesthetic. Spacing, typography,
  weights, line heights, letter spacing, radius, z-index, transitions,
  breakpoints, container widths all KEPT — neutral design primitives,
  not aesthetic choices.
- styles/_reset.scss body bg-color: #fff / color: #000 defaults.
  Designer chooses the palette; reset stays purely structural.
- styles.scss .app class wrapper.
- config.ts USERS endpoints, USERS query keys, STORAGE_KEYS,
  HTTP_STATUS, PAGINATION — template constants with no canary
  consumer. Reduced to ROUTES.HOME and QUERY_CONFIG (consumed by
  query.config.ts). Designer adds back what they actually need.

Kept (logic / infrastructure / Phase 14 work):
- Full api/ and core/api/ — Zod schemas, axios client, hooks,
  ApiError, QueryClient, retry strategies.
- main.tsx, index.html (already canary-titled with favicons in
  public/asset).
- QueryClientProvider + RouterProvider + bare Toaster + ReactQuery
  Devtools provider stack.
- Lazy-route + Suspense + ErrorBoundary pattern in shell.
- styles/_index.scss, _fonts.scss (system font stacks), _mixins.scss
  (breakpoint utilities, flex helpers, sr-only, truncate, etc.) —
  all neutral.

Gate: pnpm typecheck, pnpm biome check, pnpm lint:scss, pnpm build
all clean. Production bundle: 140-byte landing chunk (the <div />),
~37 KB index chunk (api/core/router/zod machinery), no leftover
template CSS.
This commit is contained in:
CarterPerez-dev 2026-05-17 13:12:24 -04:00
parent 37cdf97133
commit 1d205c1791
17 changed files with 19 additions and 431 deletions

View File

@ -7,29 +7,14 @@ import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { RouterProvider } from 'react-router-dom'
import { Toaster } from 'sonner'
import { queryClient } from '@/core/api'
import { router } from '@/core/app/routers'
import '@/core/app/toast.module.scss'
export default function App(): React.ReactElement {
return (
<QueryClientProvider client={queryClient}>
<div className="app">
<RouterProvider router={router} />
<Toaster
position="top-right"
duration={2000}
theme="dark"
toastOptions={{
style: {
background: 'hsl(0, 0%, 12.2%)',
border: '1px solid hsl(0, 0%, 18%)',
color: 'hsl(0, 0%, 98%)',
},
}}
/>
</div>
<RouterProvider router={router} />
<Toaster />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)

View File

@ -2,31 +2,12 @@
// ©AngelaMos | 2026
// config.ts
// ===================
const API_VERSION = 'v1'
export const API_ENDPOINTS = {
USERS: {
BASE: `/${API_VERSION}/users`,
BY_ID: (id: string) => `/${API_VERSION}/users/${id}`,
},
} as const
export const QUERY_KEYS = {
USERS: {
ALL: ['users'] as const,
BY_ID: (id: string) => [...QUERY_KEYS.USERS.ALL, 'detail', id] as const,
},
} as const
export const ROUTES = {
HOME: '/',
DASHBOARD: '/dashboard',
SETTINGS: '/settings',
} as const
export const STORAGE_KEYS = {
UI: 'ui-storage',
} as const
export type Route = typeof ROUTES
export const QUERY_CONFIG = {
STALE_TIME: {
@ -43,26 +24,3 @@ export const QUERY_CONFIG = {
NONE: 0,
},
} as const
export const HTTP_STATUS = {
OK: 200,
CREATED: 201,
NO_CONTENT: 204,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
CONFLICT: 409,
TOO_MANY_REQUESTS: 429,
INTERNAL_SERVER: 500,
} as const
export const PAGINATION = {
DEFAULT_PAGE: 1,
DEFAULT_SIZE: 20,
MAX_SIZE: 100,
} as const
export type ApiEndpoint = typeof API_ENDPOINTS
export type QueryKey = typeof QUERY_KEYS
export type Route = typeof ROUTES

View File

@ -8,27 +8,19 @@ import { ROUTES } from '@/config'
import { Shell } from './shell'
const routes: RouteObject[] = [
{
path: ROUTES.HOME,
lazy: () => import('@/pages/landing'),
},
{
element: <Shell />,
children: [
{
path: ROUTES.DASHBOARD,
lazy: () => import('@/pages/dashboard'),
path: ROUTES.HOME,
lazy: () => import('@/pages/landing'),
},
{
path: ROUTES.SETTINGS,
lazy: () => import('@/pages/settings'),
path: '*',
lazy: () => import('@/pages/landing'),
},
],
},
{
path: '*',
lazy: () => import('@/pages/landing'),
},
]
export const router = createBrowserRouter(routes)

View File

@ -1,4 +0,0 @@
// ===================
// ©AngelaMos | 2026
// shell.module.scss
// ===================

View File

@ -1,116 +1,22 @@
/**
* ©AngelaMos | 2026
* shell.tsx
*/
// ===================
// ©AngelaMos | 2026
// shell.tsx
// ===================
import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import { GiCardAceClubs, GiCardJoker } from 'react-icons/gi'
import { LuChevronLeft, LuChevronRight, LuMenu } from 'react-icons/lu'
import { NavLink, Outlet, useLocation } from 'react-router-dom'
import { ROUTES } from '@/config'
import { useUIStore } from '@/core/lib'
import styles from './shell.module.scss'
const NAV_ITEMS = [
{ path: ROUTES.DASHBOARD, label: 'Dashboard', icon: GiCardJoker },
{ path: ROUTES.SETTINGS, label: 'Settings', icon: GiCardAceClubs },
]
import { Outlet } from 'react-router-dom'
function ShellErrorFallback({ error }: { error: Error }): React.ReactElement {
return (
<div className={styles.error}>
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
</div>
)
}
function ShellLoading(): React.ReactElement {
return <div className={styles.loading}>Loading...</div>
}
function getPageTitle(pathname: string): string {
const item = NAV_ITEMS.find((i) => i.path === pathname)
return item?.label ?? 'Dashboard'
return <pre>{error.message}</pre>
}
export function Shell(): React.ReactElement {
const location = useLocation()
const { sidebarOpen, sidebarCollapsed, toggleSidebar, toggleSidebarCollapsed } =
useUIStore()
const pageTitle = getPageTitle(location.pathname)
return (
<div className={styles.shell}>
<aside
className={`${styles.sidebar} ${sidebarOpen ? styles.open : ''} ${sidebarCollapsed ? styles.collapsed : ''}`}
>
<div className={styles.sidebarHeader}>
<span className={styles.logo}>NavBar Template</span>
<button
type="button"
className={styles.collapseBtn}
onClick={toggleSidebarCollapsed}
aria-label={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{sidebarCollapsed ? <LuChevronRight /> : <LuChevronLeft />}
</button>
</div>
<nav className={styles.nav}>
{NAV_ITEMS.map((item) => (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
`${styles.navItem} ${isActive ? styles.active : ''}`
}
onClick={() => sidebarOpen && toggleSidebar()}
>
<item.icon className={styles.navIcon} />
<span className={styles.navLabel}>{item.label}</span>
</NavLink>
))}
</nav>
</aside>
{sidebarOpen && (
<button
type="button"
className={styles.overlay}
onClick={toggleSidebar}
onKeyDown={(e) => e.key === 'Escape' && toggleSidebar()}
aria-label="Close sidebar"
/>
)}
<div
className={`${styles.main} ${sidebarCollapsed ? styles.collapsed : ''}`}
>
<header className={styles.header}>
<div className={styles.headerLeft}>
<button
type="button"
className={styles.menuBtn}
onClick={toggleSidebar}
aria-label="Toggle menu"
>
<LuMenu />
</button>
<h1 className={styles.pageTitle}>{pageTitle}</h1>
</div>
</header>
<main className={styles.content}>
<ErrorBoundary FallbackComponent={ShellErrorFallback}>
<Suspense fallback={<ShellLoading />}>
<Outlet />
</Suspense>
</ErrorBoundary>
</main>
</div>
</div>
<ErrorBoundary FallbackComponent={ShellErrorFallback}>
<Suspense fallback={null}>
<Outlet />
</Suspense>
</ErrorBoundary>
)
}

View File

@ -1,4 +0,0 @@
// ===================
// ©AngelaMos | 2026
// toast.module.scss
// ===================

View File

@ -2,5 +2,3 @@
// ©AngelaMos | 2026
// index.ts
// ===================
export * from './shell.ui.store'

View File

@ -1,63 +0,0 @@
/**
* ©AngelaMos | 2026
* ui.store.ts
*/
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
type Theme = 'light' | 'dark' | 'system'
interface UIState {
theme: Theme
sidebarOpen: boolean
sidebarCollapsed: boolean
setTheme: (theme: Theme) => void
toggleSidebar: () => void
setSidebarOpen: (open: boolean) => void
toggleSidebarCollapsed: () => void
}
export const useUIStore = create<UIState>()(
devtools(
persist(
(set) => ({
theme: 'dark',
sidebarOpen: false,
sidebarCollapsed: false,
setTheme: (theme) => set({ theme }, false, 'ui/setTheme'),
toggleSidebar: () =>
set(
(state) => ({ sidebarOpen: !state.sidebarOpen }),
false,
'ui/toggleSidebar'
),
setSidebarOpen: (open) =>
set({ sidebarOpen: open }, false, 'ui/setSidebarOpen'),
toggleSidebarCollapsed: () =>
set(
(state) => ({ sidebarCollapsed: !state.sidebarCollapsed }),
false,
'ui/toggleSidebarCollapsed'
),
}),
{
name: 'ui-storage',
partialize: (state) => ({
theme: state.theme,
sidebarCollapsed: state.sidebarCollapsed,
}),
}
),
{ name: 'UIStore' }
)
)
export const useTheme = (): Theme => useUIStore((s) => s.theme)
export const useSidebarOpen = (): boolean => useUIStore((s) => s.sidebarOpen)
export const useSidebarCollapsed = (): boolean =>
useUIStore((s) => s.sidebarCollapsed)

View File

@ -1,4 +0,0 @@
// ===================
// ©AngelaMos | 2026
// dashboard.module.scss
// ===================

View File

@ -1,62 +0,0 @@
/**
* ©AngelaMos | 2026
* index.tsx
*/
import styles from './dashboard.module.scss'
const AVAILABLE_STORES = [
{
name: 'useUIStore()',
file: 'core/lib/shell.ui.store.ts',
description: 'Theme, sidebar open/collapsed state',
},
]
const SUGGESTED_FEATURES = [
'Stats and metrics',
'Recent activity feed',
'Quick actions',
'Charts and analytics',
'Notifications overview',
'Task/project summary',
]
export function Component(): React.ReactElement {
return (
<div className={styles.page}>
<div className={styles.container}>
<div className={styles.header}>
<h1 className={styles.title}>Welcome</h1>
<p className={styles.subtitle}>
Template page build your dashboard here
</p>
</div>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Available Stores</h2>
<div className={styles.grid}>
{AVAILABLE_STORES.map((store) => (
<div key={store.name} className={styles.card}>
<code className={styles.hookName}>{store.name}</code>
<p className={styles.description}>{store.description}</p>
<span className={styles.file}>{store.file}</span>
</div>
))}
</div>
</section>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Suggested Features</h2>
<ul className={styles.list}>
{SUGGESTED_FEATURES.map((feature) => (
<li key={feature}>{feature}</li>
))}
</ul>
</section>
</div>
</div>
)
}
Component.displayName = 'Dashboard'

View File

@ -3,10 +3,8 @@
// index.tsx
// ===================
import styles from './landing.module.scss'
export function Component(): React.ReactElement {
return <div className={styles.page} />
return <div />
}
Component.displayName = 'Landing'

View File

@ -1,4 +0,0 @@
// ===================
// ©AngelaMos | 2026
// landing.module.scss
// ===================

View File

@ -1,56 +0,0 @@
/**
* ©AngelaMos | 2026
* index.tsx
*/
import styles from './settings.module.scss'
const AVAILABLE_STORES = [
{
name: 'useUIStore()',
file: 'core/lib/shell.ui.store.ts',
description: 'Theme, sidebar open/collapsed state',
},
]
export function Component(): React.ReactElement {
return (
<div className={styles.page}>
<div className={styles.container}>
<div className={styles.header}>
<h1 className={styles.title}>Settings</h1>
<p className={styles.subtitle}>
Template page available stores for building your settings UI
</p>
</div>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Available Stores</h2>
<div className={styles.grid}>
{AVAILABLE_STORES.map((store) => (
<div key={store.name} className={styles.card}>
<code className={styles.hookName}>{store.name}</code>
<p className={styles.description}>{store.description}</p>
<div className={styles.meta}>
<span className={styles.file}>{store.file}</span>
</div>
</div>
))}
</div>
</section>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Suggested Features</h2>
<ul className={styles.list}>
<li>Profile form</li>
<li>Theme toggle (dark/light)</li>
<li>Notification settings</li>
<li>Application preferences</li>
</ul>
</section>
</div>
</div>
)
}
Component.displayName = 'Settings'

View File

@ -1,4 +0,0 @@
// ===================
// ©AngelaMos | 2026
// settings.module.scss
// ===================

View File

@ -12,12 +12,4 @@
#root {
min-height: 100vh;
min-height: 100dvh;
display: flex;
flex-direction: column;
}
.app {
flex: 1;
display: flex;
flex-direction: column;
}

View File

@ -38,8 +38,6 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
background-color: #fff;
color: #000;
}
h1,

View File

@ -71,44 +71,6 @@ $tracking-normal: 0;
$tracking-wide: 0.025em;
$tracking-wider: 0.05em;
// ============================================================================
// COLORS
// ============================================================================
$white: hsl(0, 0%, 100%);
$black: hsl(0, 0%, 0%);
// Auth
$bg-page: hsl(0, 0%, 10.5%);
$bg-card: hsl(0, 0%, 6.2%);
// Home/landing
$bg-landing: hsl(0, 0%, 10.8%);
$bg-default: hsl(0, 0%, 7.1%);
$bg-alternative: hsl(0, 0%, 5.9%);
$bg-surface-75: hsl(0, 0%, 9%);
$bg-surface-100: hsl(0, 0%, 12.2%);
$bg-surface-200: hsl(0, 0%, 14.1%);
$bg-surface-300: hsl(0, 0%, 16.1%);
$bg-control: hsl(0, 0%, 10%);
$bg-selection: hsl(0, 0%, 19.2%);
$bg-overlay: hsl(0, 0%, 14.1%);
$bg-overlay-hover: hsl(0, 0%, 18%);
$border-muted: hsl(0, 0%, 11.1%);
$border-default: hsl(0, 0%, 18%);
$border-strong: hsl(0, 0%, 22.4%);
$border-stronger: hsl(0, 0%, 27.1%);
$border-control: hsl(0, 0%, 22.4%);
$text-default: hsl(0, 0%, 98%);
$text-light: hsl(0, 0%, 70.6%);
$text-lighter: hsl(0, 0%, 53.7%);
$text-muted: hsl(0, 0%, 30.2%);
$error-default: hsl(0, 72%, 51%);
$error-light: hsl(0, 72%, 65%);
// ============================================================================
// BORDER RADIUS
// ============================================================================