@the-portland-company/shell 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.4 — 2026-05-14
4
+
5
+ ### Fixed
6
+
7
+ - `package.json` `exports` for `./css` and `./chrome.css` now ship a `types` entry pointing at a tiny ambient module declaration (`dist/css-modules.d.ts`). Fixes TS2882 errors in fresh consumers importing the side-effect CSS subpaths.
8
+
9
+ ### Documentation
10
+
11
+ - README install command now pins peer-dep versions explicitly (`react@^18`, `@chakra-ui/react@^2.8`, etc.) so Vite-scaffolded apps on React 19 / Chakra v3 don't silently install incompatible versions.
12
+ - Clarified `@the-portland-company/devnotes` is optional.
13
+
14
+ ## 0.3.3 — 2026-05-14
15
+
16
+ ### Documentation
17
+
18
+ - Rewrote `README.md` to cover the v0.3.x chrome contract: every component / hook / helper / type, the `<ShellChrome>` binding pattern with a copy-pasteable bridge example, the `chrome.css` + `css` import paths, multi-SPA routing notes for the politogy edge router, and a pointer to `politogy-vrm/react/app/src/providers/ShellChromeBridge.tsx` as the reference implementation. No code changes.
19
+
3
20
  ## 0.3.2 — 2026-05-13
4
21
 
5
22
  ### Added
package/README.md CHANGED
@@ -2,47 +2,203 @@
2
2
 
3
3
  Shared chrome for politogy apps. One install, one wrapper, identical look.
4
4
 
5
+ Every politogy product (Email Blast, Contacts, Forms, future apps…) installs this package and gets the full branded experience — sidebar with VRM logo, organization switcher, mode selector, full nav list, header with notifications and support, branded footer with commit info — without rebuilding any of it. Path-based routing lets multiple separately-deployed Vite SPAs feel like a single continuous app on `app.politogy.com`.
6
+
7
+ > **v0.3.x is a major rewrite.** See the [CHANGELOG](./CHANGELOG.md) for the chrome component, hook, and prop additions over v0.2.x. The minimum viable boot below works for new apps; existing v0.2.x consumers can keep using `OrgSelector`, `useShellAuth`, etc. without changes.
8
+
9
+ ---
10
+
5
11
  ## Install
6
12
 
13
+ Pin peer dep versions explicitly — Vite scaffolds React 19 + Chakra v3 by default, neither of which the shell supports:
14
+
15
+ ```bash
16
+ npm install @the-portland-company/shell \
17
+ react@^18 react-dom@^18 \
18
+ @chakra-ui/react@^2.8 @emotion/react@^11 @emotion/styled@^11 \
19
+ framer-motion@^11 \
20
+ @supabase/supabase-js@~2.95 \
21
+ react-router-dom@^6 react-icons@^5
7
22
  ```
8
- npm install @the-portland-company/shell @chakra-ui/react @emotion/react @emotion/styled @supabase/supabase-js react react-dom
9
- ```
10
23
 
11
- ## Quick start
24
+ `@the-portland-company/devnotes` is **optional** — only needed if you want the dev-notes menu in the header. Skip it if your app doesn't use it.
25
+
26
+ ---
27
+
28
+ ## Minimum viable boot
29
+
30
+ The simplest possible startup renders the branded chrome with empty defaults — the sidebar appears, the header renders, but the org switcher / mode selector / nav list show "no data" states until you wire them up via `<ShellChrome>` (next section).
12
31
 
13
32
  ```tsx
14
- import { ShellProvider, AppLayout, registerShellPrecache } from '@the-portland-company/shell'
15
- import '@the-portland-company/shell/css'
33
+ // src/main.tsx
34
+ import React from 'react'
35
+ import ReactDOM from 'react-dom/client'
36
+ import { BrowserRouter } from 'react-router-dom'
37
+ import { ShellProvider, AppLayout } from '@the-portland-company/shell'
38
+ import '@the-portland-company/shell/chrome.css'
39
+ import { createClient } from '@supabase/supabase-js'
40
+ import App from './App'
41
+
42
+ const supabase = createClient(
43
+ import.meta.env.VITE_SUPABASE_URL,
44
+ import.meta.env.VITE_SUPABASE_ANON_KEY,
45
+ )
46
+
47
+ ReactDOM.createRoot(document.getElementById('root')!).render(
48
+ <ShellProvider currentApp="email" supabaseClient={supabase}>
49
+ <BrowserRouter>
50
+ <AppLayout>
51
+ <App />
52
+ </AppLayout>
53
+ </BrowserRouter>
54
+ </ShellProvider>,
55
+ )
56
+ ```
16
57
 
17
- registerShellPrecache().catch(() => {})
58
+ `<AppLayout>` composes the shell `<Header>`, `<Sidebar>`, your routed content, and `<Footer>`. `<ShellProvider>` owns the Supabase auth lifecycle and exposes `useShellAuth()` for status / user / sign-out.
18
59
 
19
- const APPS = [
20
- { id: 'crm', label: 'CRM', path: '/' },
21
- { id: 'messaging', label: 'Messaging', path: '/messaging' },
22
- ]
60
+ ---
23
61
 
24
- export default function App() {
62
+ ## Real-world boot: feeding the chrome real data
63
+
64
+ Most apps have an `<AuthProvider>`, `<OrganizationProvider>`, `<ModeProvider>`, etc. that live *below* `<ShellProvider>` in the tree. To push their data up into the shell's chrome contexts, drop a `<ShellChrome>` binding component as deep in the tree as the data is available:
65
+
66
+ ```tsx
67
+ import {
68
+ ShellProvider,
69
+ ShellChrome,
70
+ AppLayout,
71
+ } from '@the-portland-company/shell'
72
+
73
+ function ChromeBridge({ children }) {
74
+ const { currentMode, switchMode, getModeConfig, getAvailableModes } = useMode()
75
+ const { currentOrg, organizations, switchOrganization } = useOrganization()
76
+ const { linkedAccounts, ... } = useLinkedAccounts()
77
+ // …compute the nav-items list for the active mode here…
25
78
  return (
26
- <ShellProvider currentApp="crm" supabaseClient={supabase} appRegistry={APPS}>
27
- <AppLayout>{/* your routes */}</AppLayout>
28
- </ShellProvider>
79
+ <ShellChrome
80
+ mode={{ currentMode, modes: getAvailableModes(), switchMode, getModeConfig }}
81
+ organization={{ currentOrg, organizations, switchOrganization }}
82
+ linkedAccounts={{ linkedAccounts, switchToAccount, onAddAccount, onRemoveAccount }}
83
+ navItems={navItems}
84
+ role={{ activeRoleConfig, isSuperAdmin }}
85
+ footer={{ links, commit, version, releaseName, environmentLabel }}
86
+ support={{ submitSupportRequest }}
87
+ brandLogoSrc="/brand/logos/email-mode-logo.png"
88
+ headerIconsSlot={<MyAppSpecificIcons />}
89
+ userAvatarUrl={profile.avatar_url ?? storedMemojiUrl ?? defaultAvatarUrl}
90
+ myProfileHref="/my-profile"
91
+ organizationSettingsHref={`/organization-settings?org=${currentOrg.id}`}
92
+ >
93
+ {children}
94
+ </ShellChrome>
29
95
  )
30
96
  }
97
+
98
+ // Then in main.tsx:
99
+ <ShellProvider currentApp="email" supabaseClient={supabase}>
100
+ <BrowserRouter>
101
+ <AuthProvider>
102
+ <OrganizationProvider>
103
+ <ModeProvider>
104
+ <ChromeBridge>
105
+ <AppLayout>
106
+ <Routes>…</Routes>
107
+ </AppLayout>
108
+ </ChromeBridge>
109
+ </ModeProvider>
110
+ </OrganizationProvider>
111
+ </AuthProvider>
112
+ </BrowserRouter>
113
+ </ShellProvider>
31
114
  ```
32
115
 
33
- ## Slot props on `<AppLayout>`
116
+ **Reference implementation:** `politogy-vrm`'s `react/app/src/providers/ShellChromeBridge.tsx` (~600 lines, every chrome wiring pattern). Copy as a starting point for new apps.
117
+
118
+ All `<ShellChrome>` props are optional — only supply what your app has. The chrome gracefully omits the bits it doesn't get data for (e.g., no `support` prop → no support icon rendered).
119
+
120
+ ---
121
+
122
+ ## What the shell exports
123
+
124
+ ### Components
125
+
126
+ | Export | Use |
127
+ |---|---|
128
+ | `<ShellProvider>` | Top-level provider. Wraps Chakra + auth + meta contexts. |
129
+ | `<ShellChrome>` | Mid-tree binding component. Feeds chrome contexts from app data. |
130
+ | `<AppLayout>` | Composes Header + Sidebar + main content + Footer. |
131
+ | `<Header>` | Header bar (logo, page title, breadcrumbs, icon stack, user menu). |
132
+ | `<Sidebar>` | Sidebar (branded logo, org pill, mode pill, nav list, footer). |
133
+ | `<Footer>` | Below main content. Usually not rendered separately. |
134
+ | `<UserMenu>` | Avatar dropdown (profile, theme, sign out, switch account). |
135
+ | `<OrgPill>` (alias `<OrgSelector>`) | Organization switcher pill rendered in the sidebar. |
136
+ | `<ModePill>` | Mode (Relationship / Campaign / Petition) switcher. |
137
+ | `<NavItem>` | Single sidebar nav row with active state + hover gradient. |
138
+ | `<SupportRequestButton>` | Header life-buoy icon that opens a support modal. |
139
+ | `<BrandLogo>` | `<img>` wrapper for the branded mode logo. |
140
+ | `<BrandIcon name="...">` | Renders `/brand/icons/<name>.svg` from your `public/`. |
141
+ | `<CommitCopyButton>` | Footer commit/version display with copy-to-clipboard. |
142
+ | `<Card>`, `<PageContainer>`, `<PageHeader>`, `<BreadcrumbBar>`, `<HeaderTabBar>` | Page-level layout primitives. |
143
+ | `<GlobalUiIdProvider>` | Assigns stable UI IDs (for testing/analytics). |
144
+
145
+ ### Hooks
146
+
147
+ | Hook | Returns |
148
+ |---|---|
149
+ | `useShellAuth()` | `{ status, user, session, signOut }` |
150
+ | `useShellOrganization()` | `{ currentOrg, organizations, switchOrganization, createOrgHref } \| null` |
151
+ | `useShellMode()` | `{ currentMode, modes, switchMode, getModeConfig } \| null` |
152
+ | `useShellNavItems()` | `{ items: ShellNavItem[] } \| null` |
153
+ | `useShellLinkedAccounts()` | `{ linkedAccounts, switchToAccount, onAddAccount, onRemoveAccount } \| null` |
154
+ | `useShellSupport()` | `{ submitSupportRequest } \| null` |
155
+ | `useShellRole()` | `{ activeRoleConfig, isSuperAdmin } \| null` |
156
+ | `useShellFooter()` | `{ links, version, commit, releaseName, … } \| null` |
157
+ | `useShellNavigationPreference()` | `{ showSubMenu } \| null` |
158
+ | `useShellChrome()` | Chrome configuration (`devNotesMenu`, `myProfileHref`, `userAvatarUrl`, `pageTitle`, `breadcrumbs`, `headerActions`, etc.) |
159
+ | `useAppRegistry()`, `useCurrentApp()` | App switcher metadata for cross-app speculation rules. |
160
+
161
+ ### Helpers
34
162
 
35
- | Prop | Position | Use for |
36
- |---|---|---|
37
- | `headerExtrasSlot` | Right of header, before user menu | App-specific header widgets |
38
- | `modalsSlot` | Above main content, position absolute | Modal dialogs |
39
- | `notificationsSlot` | Inside main, top-right | Toasts, badges |
40
- | `sidebarFooterSlot` | Bottom of sidebar | Org switcher, status |
41
- | `overlaySlot` | Top of everything | Always-on-top indicators |
163
+ `normalizeHexColor`, `lightenHexColor`, `hexToRgba`, `getOrganizationBrandColor`, `getOrganizationBadgeLetters`, `getUserDisplayName`.
42
164
 
43
- ## Service worker
165
+ ### Theme
44
166
 
45
- The package ships a precache SW. Copy it into your `public/` dir during postinstall:
167
+ `politogyTheme` — Chakra theme with brand palette, mode-aware semantic tokens, and component overrides.
168
+
169
+ ---
170
+
171
+ ## CSS imports
172
+
173
+ ```tsx
174
+ import '@the-portland-company/shell/chrome.css' // :root design tokens (--app-color-brand-*, --mode-*, --app-tab-*, --form-control-*)
175
+ import '@the-portland-company/shell/css' // view-transitions CSS for cross-app polish
176
+ ```
177
+
178
+ `chrome.css` is required for theme variables. `css` (view-transitions) is optional polish.
179
+
180
+ ---
181
+
182
+ ## Multi-SPA routing on `app.politogy.com`
183
+
184
+ Multiple politogy apps live behind one domain via a Cloudflare Worker edge router (in `packages/edge-router` of the monorepo).
185
+
186
+ When you deploy a new app to Cloudflare Pages, add its path prefix to the edge router's `routes.ts`:
187
+
188
+ ```ts
189
+ {
190
+ pathPrefix: '/email',
191
+ origin: 'https://politogy-email.pages.dev',
192
+ }
193
+ ```
194
+
195
+ After deploy, users hitting `app.politogy.com/email/*` get transparently proxied to your app. Other paths keep going to whichever app owns them. The shared shell + same Supabase session means it feels like one app.
196
+
197
+ ---
198
+
199
+ ## Service worker (optional polish)
200
+
201
+ The package ships a precache service worker that caches the shell chrome on first visit so cross-app navigation feels instant.
46
202
 
47
203
  ```js
48
204
  // scripts/copy-shell-sw.mjs
@@ -53,11 +209,13 @@ await copyFile(
53
209
  )
54
210
  ```
55
211
 
56
- Then call `registerShellPrecache()` at the top of your app.
212
+ Wire it into your build (`"postinstall": "node scripts/copy-shell-sw.mjs"`) and call `registerShellPrecache()` once on app boot.
213
+
214
+ ---
57
215
 
58
- ## Speculation Rules
216
+ ## Speculation Rules (optional polish)
59
217
 
60
- Emit cross-app prerendering hints:
218
+ Cross-app prerendering hints for the browser:
61
219
 
62
220
  ```tsx
63
221
  import { useAppRegistry, useCurrentApp, buildSpeculationRules } from '@the-portland-company/shell'
@@ -70,3 +228,16 @@ function SpeculationRules() {
70
228
  return <script type="speculationrules" dangerouslySetInnerHTML={{ __html: json }} />
71
229
  }
72
230
  ```
231
+
232
+ Render once near the app root.
233
+
234
+ ---
235
+
236
+ ## Reference + support
237
+
238
+ - **Reference bridge:** `politogy-vrm/react/app/src/providers/ShellChromeBridge.tsx` — full chrome wiring for the main politogy app.
239
+ - **Architectural decisions, gotchas, session log:** `politogy-vrm/docs/superpowers/`.
240
+ - **Type definitions:** `node_modules/@the-portland-company/shell/dist/index.d.ts` — every prop and hook is fully typed.
241
+ - **CHANGELOG:** [CHANGELOG.md](./CHANGELOG.md) — every export added per version.
242
+
243
+ Questions or missing features: file an issue or ping the politogy team.
@@ -0,0 +1,6 @@
1
+ // Side-effect type declarations for the CSS subpath exports so TS consumers
2
+ // can `import '@the-portland-company/shell/css'` and
3
+ // `import '@the-portland-company/shell/chrome.css'` without ambient-shim
4
+ // boilerplate.
5
+ declare module '@the-portland-company/shell/css'
6
+ declare module '@the-portland-company/shell/chrome.css'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-portland-company/shell",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Shared chrome (header, sidebar, footer, theme, auth) for politogy apps. Drop into any Vite SPA to inherit the politogy look and signed-in user.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -17,8 +17,14 @@
17
17
  "import": "./dist/index.js",
18
18
  "require": "./dist/index.cjs"
19
19
  },
20
- "./css": "./dist/view-transitions.css",
21
- "./chrome.css": "./dist/chrome.css",
20
+ "./css": {
21
+ "types": "./dist/css-modules.d.ts",
22
+ "default": "./dist/view-transitions.css"
23
+ },
24
+ "./chrome.css": {
25
+ "types": "./dist/css-modules.d.ts",
26
+ "default": "./dist/chrome.css"
27
+ },
22
28
  "./sw": "./dist/shell-precache.worker.js"
23
29
  },
24
30
  "files": [
@@ -30,7 +36,7 @@
30
36
  "*.css"
31
37
  ],
32
38
  "scripts": {
33
- "build": "tsup",
39
+ "build": "tsup && cp src/css-modules.d.ts dist/css-modules.d.ts",
34
40
  "dev": "tsup --watch",
35
41
  "test": "vitest run",
36
42
  "test:watch": "vitest",