diff --git a/docs/tasks/project-branding.md b/docs/tasks/project-branding.md
new file mode 100644
index 0000000..8a89f67
--- /dev/null
+++ b/docs/tasks/project-branding.md
@@ -0,0 +1,86 @@
+# Project branding — the Admin Portal shows the project's name and logo
+
+Item C of the Norbix "Project" coverage campaign. Branch `audit/project-branding`
+(admin repo, from `origin/main`).
+
+## Goal
+
+The portal shell, every sign-in screen and the browser tab show the project's
+name and logo (from the gateway's public config) instead of "Account",
+"Norbix" or "Admin Portal".
+
+Not in scope: new branding fields on the gateway, dark mode, a mobile header
+(the portal has none today — see Findings), the colour tokens (already applied).
+
+## Plan
+
+1. docs(admin:branding): this task file — done
+2. feat(admin:branding): `AppLayout` sidebar header shows the logo (alt = project name) or the project name; every `AuthLayout` use passes `brandName` + `logoUrl` from the store — done, `src/components/layouts.tsx`, `src/features/auth/passwordReset.tsx`, `src/features/auth/callback.tsx`. Committed together with step 3 as `[2-3]` (one shared helper `projectNameOf` in `projectConfig.ts`).
+3. feat(admin:branding): `applyBranding` sets `document.title` to the project name; the static metadata `Admin Portal` stays as the pre-load fallback — done, `src/config/projectConfig.ts`
+4. test(admin:branding): vitest — public-config mapping (name fallback order, branding absent / present, empty logo, endpoint fails), `projectNameOf`, `applyBranding` (title, favicon, CSS variables) — done, `src/config/projectConfig.branding.test.ts` (12 tests)
+5. test(admin:branding): fake host serves its own SVG logo and returns a `branding` block (logo + icon, no colours); `tests/e2e/branding.spec.ts` (3 tests) + goldens `branding-sign-in` and `branding-shell` (`-chromium-darwin`) — done
+6. chore(admin:branding): checks green, push, pull request — done (results below)
+7. feat(admin:branding): `dashboard.tsx:42` "Norbix Admin" → project name — dropped: that line is the `Placeholder` shown only when NO project resolved, so there is no name to show (see Rejected)
+
+### Results (step 6)
+
+- `npm run lint` — pass
+- `npm run format:check` — pass
+- `npm test` (vitest) — 13 files, 92 tests pass
+- `npm run test:e2e` (fake host) — 6 / 6 pass (3 branding + 3 chat); the chat goldens did NOT change (the chat screenshots cover only the chat region, and the fake brand has no colours)
+- `npm run typecheck` — 6 errors, all inside `../sdks/norbix-react-redux` and the same 6 on `origin/main` without this change (see Findings). Zero errors in `src/`.
+
+### Local setup (not committed)
+
+`package.json` and `tsconfig*.json` point at `../sdks/…`. From this worktree that is
+`worktrees/admin/audit/project/sdks`, so a scratch symlink was made OUTSIDE the
+repo: `ln -s ~/Projects/norbix/sdks ~/Projects/norbix/worktrees/admin/audit/project/sdks`,
+then `npm ci`. No change to the SDK link paths. Logs: `~/scratch/project-branding/`.
+
+## Changes
+
+| file | what changed | plan step # |
+|------|--------------|-------------|
+| `docs/tasks/project-branding.md` | this file | 1, 6 |
+| `src/config/projectConfig.ts` | `PLACEHOLDER_DISPLAY_NAME` + `projectNameOf()` (name, or undefined for the "Sign in" placeholder); `applyBranding` sets `document.title` | 2, 3 |
+| `src/components/layouts.tsx` | `AppLayout` sidebar header: logo `` or the name; "Account" only when no name; `data-testid="sidebar-brand"` | 2 |
+| `src/features/auth/passwordReset.tsx` | both screens pass `brandName` + `logoUrl` from `selectProjectBranding` | 2 |
+| `src/features/auth/callback.tsx` | passes `brandName` + `logoUrl` from the store | 2 |
+| `src/config/projectConfig.branding.test.ts` | new vitest file (jsdom), 12 tests | 4 |
+| `tests/e2e/fake-api-host.mjs` | serves `/__assets/logo.svg`; config gets `branding { displayName, logoUrl, iconUrl }` | 5 |
+| `tests/e2e/branding.spec.ts` | new: sign-in, password reset, signed-in shell; tab title; favicon | 5 |
+| `tests/e2e/branding.spec.ts-snapshots/branding-{sign-in,shell}-chromium-darwin.png` | new goldens | 5 |
+
+## Findings
+
+- fix(sdks:react-redux): the local SDK `main` checkouts do not typecheck together — `norbix-react-redux` calls `getEmailCampaignMessage` / `getSmsCampaignMessage` / `getPushCampaignMessage`, which `norbix-js` `main` (b7230dc) does not have — open, `sdks/norbix-react-redux/src/hooks/hub/notifications.ts:63`. `npm run typecheck` in admin fails with these 6 errors on `origin/main` too.
+ ```ts
+ type GetEmailCampaignMessage = Norbix['hub']['notifications']['getEmailCampaignMessage']; // <-- here: not on NotificationsModule (only …Messages)
+ ```
+- chore(admin:ci): CI never runs the Playwright suite, so the `-darwin` goldens are only checked on a Mac — open, `.github/workflows/ci.yml` (steps: lint, format, typecheck, test, build; no `test:e2e`). If e2e is added on `ubuntu-latest`, `-linux` goldens are needed.
+- chore(admin:ci): CI checks out only the admin repo, yet `package.json` links `file:../sdks/norbix-js` — open, not checked here how `npm ci` resolves it on GitHub.
+ ```json
+ "@norbix.ai/ts": "file:../sdks/norbix-js", // <-- here: no ../sdks on the CI runner
+ ```
+- fix(admin:branding): the placeholder name is a page label ("Sign in"), so a project with no name shows "Sign in to Sign in" on the login heading — open, `src/features/auth/login.tsx:98` + `src/config/projectConfig.ts` `PLACEHOLDER_DISPLAY_NAME`. The new sidebar and tab title avoid it via `projectNameOf`; the login heading was left as it was (out of scope).
+ ```tsx
+ Sign in to {config.branding.displayName} {/* <-- here: "Sign in to Sign in" when the project has no name */}
+ ```
+- note(admin:layout): the task brief mentioned a mobile header at `layouts.tsx:~103` — there is none. That line is the "Account" label of the sign-out menu at the bottom of the sidebar; kept as is (it is the user's account menu, not the brand) — `src/components/layouts.tsx:120`.
+- note(admin:layout): the sidebar is a fixed `w-60` column with no small-screen variant — open, `src/components/layouts.tsx` `