Skip to content

Commit 0beff1a

Browse files
authored
feat(shared): make @shared the single home for brand logo assets (Stirling-Tools#6714)
## What Makes `@shared` the single home for the Stirling brand logo assets. Moves the editor's two logo sets — `classic-logo` + `modern-logo` (22 files: marks, wordmarks, favicons, login headers, PNGs) — out of `editor/public/` into `shared/assets/brand/`, and adds a Storybook **Brand/Logos** gallery. ## Why this shape (not a plain move) The editor serves logos by **URL** from `public/` and switches `classic`/`modern` by a **user preference** (`useLogoAssets`, `manifest.json` / `manifest-classic.json`, `index.html` favicon links). Rewiring all that to module imports would be a large, risky change to the variant system. Instead the editor keeps its variant system **unchanged** and just sources the files from shared: `vite-plugin-static-copy` copies `shared/assets/brand/{classic,modern}-logo/*` back to the served `/{classic,modern}-logo` paths (the editor already uses this plugin for pdfium/pdfjs assets). Single source of truth in shared, zero editor code/manifest/markup changes. ## Verified - **Build:** editor builds with both sets present at `dist/{modern,classic}-logo/`; `manifest.json` + favicon refs resolve. - **Dev:** the vite dev server serves the bridged paths — `/modern-logo/logo512.png`, `/modern-logo/StirlingPDFLogoNoTextDark.svg`, `/classic-logo/favicon.ico` all return **HTTP 200** (the plugin's dev middleware). - Typecheck clean on core/proprietary/saas; prettier clean; `storybook build` succeeds with the `Brand/Logos` gallery bundled. - The portal's existing `@shared/assets` brand imports are untouched. ## Follow-ups (not in this PR) - **Dedup:** `shared/assets/stirling-mark-*.svg` is byte-identical to `brand/modern-logo/StirlingPDFLogoNoTextDark.svg`, and `stirling-pdf-logo-*` is a near-twin of the modern wordmark. Reconciling these (and re-pointing the portal) needs a designer eye on which wordmark is canonical, so it's left out here to avoid changing the portal's rendered logo. - `editor/src/logo.svg` appears unused (no references) — candidate for deletion separately.
1 parent 425b76e commit 0beff1a

36 files changed

Lines changed: 131 additions & 25 deletions

frontend/editor/src/core/hooks/useLogoAssets.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import type { LogoVariant } from "@app/services/preferencesService";
1010
*/
1111
describe("useLogoAssets - Logo Asset Files", () => {
1212
const publicDir = path.resolve(__dirname, "../../../public");
13+
// Brand logo assets live in the shared design system; the editor's vite
14+
// config copies shared/assets/brand/<folder>/* into the served root at build
15+
// time (see viteStaticCopy in editor/vite.config.ts), so useLogoAssets can
16+
// keep referencing them by their public-URL path. Validate them at source.
17+
const brandDir = path.resolve(__dirname, "../../../../shared/assets/brand");
1318

1419
// All asset files that useLogoAssets references
1520
const requiredAssets = [
@@ -27,7 +32,7 @@ describe("useLogoAssets - Logo Asset Files", () => {
2732

2833
describe.each(logoVariants)("%s logo variant", (variant) => {
2934
const folder = LOGO_FOLDER_BY_VARIANT[variant];
30-
const folderPath = path.join(publicDir, folder);
35+
const folderPath = path.join(brandDir, folder);
3136

3237
test(`folder "${folder}" should exist`, () => {
3338
expect(fs.existsSync(folderPath)).toBe(true);

frontend/editor/src/proprietary/routes/Login.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
import SpringLoginForm from "@shared/auth/ui/SpringLoginForm";
2323
import { useSpringLogin } from "@shared/auth/ui/useSpringLogin";
2424
import LoggedInState from "@app/routes/login/LoggedInState";
25-
import loginHeader from "@shared/assets/login/LoginLightModeHeader.svg";
25+
import loginHeader from "@shared/assets/brand/modern-logo/LoginLightModeHeader.svg";
2626

2727
export default function Login() {
2828
const navigate = useNavigate();

frontend/editor/src/proprietary/routes/Signup.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
SignupFieldErrors,
1717
} from "@app/routes/signup/SignupFormValidation";
1818
import { useAuthService } from "@app/routes/signup/AuthService";
19-
import loginHeader from "@shared/assets/login/LoginLightModeHeader.svg";
19+
import loginHeader from "@shared/assets/brand/modern-logo/LoginLightModeHeader.svg";
2020

2121
export default function Signup() {
2222
const navigate = useNavigate();

frontend/editor/src/saas/routes/Login.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import ErrorMessage from "@shared/auth/ui/ErrorMessage";
1919
import EmailPasswordForm from "@app/routes/login/EmailPasswordForm";
2020
import OAuthButtons from "@app/routes/login/OAuthButtons";
2121
import LoggedInState from "@app/routes/login/LoggedInState";
22-
import loginHeader from "@shared/assets/login/LoginLightModeHeader.svg";
22+
import loginHeader from "@shared/assets/brand/modern-logo/LoginLightModeHeader.svg";
2323

2424
export default function Login() {
2525
const navigate = useNavigate();

frontend/editor/src/saas/routes/OAuthConsent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import "@shared/auth/ui/auth.css";
88
import "@app/routes/authShared/saas-auth.css";
99
import { withBasePath } from "@app/constants/app";
1010
import ErrorMessage from "@shared/auth/ui/ErrorMessage";
11-
import loginHeader from "@shared/assets/login/LoginLightModeHeader.svg";
11+
import loginHeader from "@shared/assets/brand/modern-logo/LoginLightModeHeader.svg";
1212

1313
/**
1414
* OAuth 2.1 consent screen for the Supabase OAuth server (used by MCP clients

frontend/editor/src/saas/routes/Signup.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
SignupFieldErrors,
2020
} from "@app/routes/signup/SignupFormValidation";
2121
import { useAuthService } from "@app/routes/signup/AuthService";
22-
import loginHeader from "@shared/assets/login/LoginLightModeHeader.svg";
22+
import loginHeader from "@shared/assets/brand/modern-logo/LoginLightModeHeader.svg";
2323

2424
export default function Signup() {
2525
const navigate = useNavigate();

frontend/editor/vite.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,17 @@ export default defineConfig(async ({ mode }) => {
271271
src: "../node_modules/pdfjs-dist/standard_fonts/*",
272272
dest: "pdfjs/standard_fonts",
273273
},
274+
{
275+
// Brand assets live in the shared design system; the editor serves
276+
// them by URL per variant, so copy each set to the /{variant}-logo
277+
// path its manifests, index.html and useLogoAssets resolve against.
278+
src: "../shared/assets/brand/classic-logo/*",
279+
dest: "classic-logo",
280+
},
281+
{
282+
src: "../shared/assets/brand/modern-logo/*",
283+
dest: "modern-logo",
284+
},
274285
],
275286
}),
276287
compressStaticCopyPlugin(),

frontend/portal/src/components/LoginScreen.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import SpringLoginForm from "@shared/auth/ui/SpringLoginForm";
77
import { useSpringLogin } from "@shared/auth/ui/useSpringLogin";
88
import "@shared/auth/ui/auth-theme.css";
99
import "@shared/auth/ui/auth.css";
10-
import loginHeader from "@shared/assets/login/LoginLightModeHeader.svg";
10+
import loginHeader from "@shared/assets/brand/modern-logo/LoginLightModeHeader.svg";
1111

1212
/**
1313
* Full-screen login shown by the portal's auth gate. Renders the same screen as

frontend/portal/src/components/Sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { useLink } from "@portal/contexts/LinkContext";
88
import { useAsync } from "@portal/hooks/useAsync";
99
import { fetchHomeKpis, type KpiEntry } from "@portal/api/home";
1010
import { EDITOR_URL } from "@portal/auth/editorUrl";
11-
import markLight from "@shared/assets/stirling-mark-light.svg";
12-
import markDark from "@shared/assets/stirling-mark-dark.svg";
11+
import markLight from "@shared/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg";
12+
import markDark from "@shared/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg";
1313
import {
1414
HomeIcon,
1515
UsersIcon,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import type { Meta, StoryObj } from "@storybook/react-vite";
2+
3+
// Visual catalogue of the shared brand assets (shared/assets/brand) — the single
4+
// source of truth for the Stirling logos used across the apps.
5+
import modernMarkDark from "@shared/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg";
6+
import modernMarkLight from "@shared/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg";
7+
import modernBlack from "@shared/assets/brand/modern-logo/StirlingPDFLogoBlackText.svg";
8+
import modernWhite from "@shared/assets/brand/modern-logo/StirlingPDFLogoWhiteText.svg";
9+
import modernGrey from "@shared/assets/brand/modern-logo/StirlingPDFLogoGreyText.svg";
10+
import classicMarkDark from "@shared/assets/brand/classic-logo/StirlingPDFLogoNoTextDark.svg";
11+
import classicMarkLight from "@shared/assets/brand/classic-logo/StirlingPDFLogoNoTextLight.svg";
12+
import classicBlack from "@shared/assets/brand/classic-logo/StirlingPDFLogoBlackText.svg";
13+
import classicWhite from "@shared/assets/brand/classic-logo/StirlingPDFLogoWhiteText.svg";
14+
import classicGrey from "@shared/assets/brand/classic-logo/StirlingPDFLogoGreyText.svg";
15+
16+
type Asset = { label: string; src: string; onDark?: boolean };
17+
type VariantSet = { variant: string; mark: Asset[]; wordmark: Asset[] };
18+
19+
const SETS: VariantSet[] = [
20+
{
21+
variant: "modern",
22+
mark: [
23+
{ label: "NoTextDark", src: modernMarkDark },
24+
{ label: "NoTextLight", src: modernMarkLight, onDark: true },
25+
],
26+
wordmark: [
27+
{ label: "BlackText", src: modernBlack },
28+
{ label: "GreyText", src: modernGrey },
29+
{ label: "WhiteText", src: modernWhite, onDark: true },
30+
],
31+
},
32+
{
33+
variant: "classic",
34+
mark: [
35+
{ label: "NoTextDark", src: classicMarkDark },
36+
{ label: "NoTextLight", src: classicMarkLight, onDark: true },
37+
],
38+
wordmark: [
39+
{ label: "BlackText", src: classicBlack },
40+
{ label: "GreyText", src: classicGrey },
41+
{ label: "WhiteText", src: classicWhite, onDark: true },
42+
],
43+
},
44+
];
45+
46+
function Swatch({ label, src, onDark, h }: Asset & { h: number }) {
47+
return (
48+
<figure
49+
style={{ margin: 0, display: "grid", gap: 6, justifyItems: "center" }}
50+
>
51+
<div
52+
style={{
53+
display: "grid",
54+
placeItems: "center",
55+
padding: 16,
56+
minWidth: 140,
57+
borderRadius: 8,
58+
border: "1px solid rgba(128,128,128,0.25)",
59+
background: onDark ? "#1a1a1a" : "#ffffff",
60+
}}
61+
>
62+
<img src={src} alt={label} style={{ height: h, maxWidth: 200 }} />
63+
</div>
64+
<figcaption style={{ fontSize: 12, color: "var(--text-muted, #71717a)" }}>
65+
{label}
66+
</figcaption>
67+
</figure>
68+
);
69+
}
70+
71+
function Row({ title, items }: { title: string; items: Asset[] }) {
72+
return (
73+
<div style={{ display: "grid", gap: 8 }}>
74+
<h4 style={{ margin: 0, textTransform: "capitalize" }}>{title}</h4>
75+
<div style={{ display: "flex", gap: 16, flexWrap: "wrap" }}>
76+
{items.map((a) => (
77+
<Swatch key={a.label} {...a} h={title === "mark" ? 48 : 28} />
78+
))}
79+
</div>
80+
</div>
81+
);
82+
}
83+
84+
const meta: Meta = {
85+
title: "Brand/Logos",
86+
parameters: { layout: "padded" },
87+
};
88+
export default meta;
89+
type Story = StoryObj;
90+
91+
/** Every brand mark + wordmark, per variant, on the background each is built for. */
92+
export const Logos: Story = {
93+
render: () => (
94+
<div style={{ display: "grid", gap: 32 }}>
95+
{SETS.map((set) => (
96+
<section key={set.variant} style={{ display: "grid", gap: 16 }}>
97+
<h3 style={{ margin: 0, textTransform: "capitalize" }}>
98+
{set.variant}
99+
</h3>
100+
<Row title="mark" items={set.mark} />
101+
<Row title="wordmark" items={set.wordmark} />
102+
</section>
103+
))}
104+
</div>
105+
),
106+
};

0 commit comments

Comments
 (0)