diff --git a/artifacts/tx-os/src/index.css b/artifacts/tx-os/src/index.css index 19678d83..7226aeb7 100644 --- a/artifacts/tx-os/src/index.css +++ b/artifacts/tx-os/src/index.css @@ -235,6 +235,8 @@ .icon-tile-orange { background: linear-gradient(135deg, #FDBA74, #F59E0B); } .icon-tile-pink { background: linear-gradient(135deg, #F9A8D4, #EC4899); } .icon-tile-teal { background: linear-gradient(135deg, #5EEAD4, #14B8A6); } +.icon-tile-red { background: linear-gradient(135deg, #FCA5A5, #EF4444); } +.icon-tile-yellow { background: linear-gradient(135deg, #FDE68A, #EAB308); } /* Top bar variant — flat very-light glass, no heavy border */ .topbar-glass { diff --git a/artifacts/tx-os/src/pages/home.tsx b/artifacts/tx-os/src/pages/home.tsx index 247b525f..8f5d42c3 100644 --- a/artifacts/tx-os/src/pages/home.tsx +++ b/artifacts/tx-os/src/pages/home.tsx @@ -81,6 +81,15 @@ function resolveIcon(name: string): LucideIcon { function gradientForColor(color: string): string { const c = (color ?? "").toLowerCase(); + // #603: each built-in app now ships with a distinct hue so the four + // default tiles (Notes/Services/Meetings/Admin) read as four different + // colours instead of three blues + one orange. New patterns added for + // yellow (#eab308 / facc15) and red (#ef4444 / dc2626), and the green + // branch already accepts the new #22c55e Meetings colour. The purple + // branch stays for any admin who manually picks a purple via the + // Apps editor, but no seeded row uses it any more. + if (c.includes("ef4444") || c.includes("dc2626") || c.includes("red")) return "icon-tile-red"; + if (c.includes("eab308") || c.includes("facc15") || c.includes("yellow")) return "icon-tile-yellow"; if (c.includes("a855f7") || c.includes("8b5cf6") || c.includes("purple") || c.includes("violet")) return "icon-tile-purple"; if (c.includes("10b981") || c.includes("22c55e") || c.includes("green") || c.includes("emerald")) return "icon-tile-green"; if (c.includes("f59e0b") || c.includes("f97316") || c.includes("orange") || c.includes("amber")) return "icon-tile-orange"; diff --git a/scripts/package.json b/scripts/package.json index 5b266007..3f99d764 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -8,6 +8,7 @@ "seed": "tsx ./src/seed.ts", "remove-chat": "tsx ./src/remove-chat.ts", "disable-deprecated-apps": "tsx ./src/disable-deprecated-apps.ts", + "update-app-colors": "tsx ./src/update-app-colors.ts", "typecheck": "tsc -p tsconfig.json --noEmit", "test": "node --test 'tests/**/*.test.mjs'" }, diff --git a/scripts/redeploy.sh b/scripts/redeploy.sh index 9bf6de25..9ab5e194 100755 --- a/scripts/redeploy.sh +++ b/scripts/redeploy.sh @@ -72,13 +72,13 @@ fi step() { echo; echo "==> $*"; } if [ "$PULL" -eq 1 ]; then - step "1/4 git pull" + step "1/6 git pull" git pull --ff-only else - step "1/4 git pull (skipped via --no-pull)" + step "1/6 git pull (skipped via --no-pull)" fi -step "2/5 docker compose build api web (rebuilds SPA + API images)" +step "2/6 docker compose build api web (rebuilds SPA + API images)" # Stamp the API image with the current git SHA + UTC build time so the # admin "System Updates" panel shows visible proof that the new image # is actually running (instead of the old, hand-edited version.json). @@ -88,14 +88,23 @@ export BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "[redeploy] GIT_SHA=$GIT_SHA BUILD_DATE=$BUILD_DATE" "${DC[@]}" build api web -step "3/5 docker compose run --rm migrate (schema push + tolerant seed)" +step "3/6 docker compose run --rm migrate (schema push + tolerant seed)" "${DC[@]}" run --rm migrate -step "4/5 explicit seed pass (fails hard on seed regressions)" +step "4/6 explicit seed pass (fails hard on seed regressions)" "${DC[@]}" run --rm --no-deps --entrypoint "" migrate \ pnpm --filter @workspace/scripts run seed -step "5/5 docker compose up -d (restart with the new images)" +step "5/6 one-shot data migrations (idempotent)" +# #603: bump seeded app tile colours on existing installs. The seed +# step above uses onConflictDoNothing for `slug`, so already-seeded +# rows keep their old colours forever without this. The script only +# touches rows that still hold the OLD default — admin customisations +# from the Apps editor are left alone. +"${DC[@]}" run --rm --no-deps --entrypoint "" migrate \ + pnpm --filter @workspace/scripts run update-app-colors + +step "6/6 docker compose up -d (restart with the new images)" "${DC[@]}" up -d echo diff --git a/scripts/src/seed.ts b/scripts/src/seed.ts index c3dbb0f7..37be6844 100644 --- a/scripts/src/seed.ts +++ b/scripts/src/seed.ts @@ -230,7 +230,11 @@ async function main() { descriptionEn: "Manage your notifications and alerts", iconName: "Bell", route: "/notifications", - color: "#8b5cf6", + // #603: was #8b5cf6 (purple). Switched to cyan so no seeded app + // ships with a purple tile. Existing installs are migrated by + // scripts/src/update-app-colors.ts (only rows still on the old + // default are touched — admin customisations are preserved). + color: "#06b6d4", // #571: ship the Notifications app DISABLED by default so fresh // installs don't surface a redundant tile alongside the bell icon // already in the top bar. The bell stays the canonical entry @@ -313,7 +317,12 @@ async function main() { descriptionEn: "Schedule and track meetings", iconName: "CalendarClock", route: "/meetings", - color: "#0B1E3F", + // #603: was #0B1E3F (dark navy) which fell through to the default + // blue tile gradient, making Meetings indistinguishable from + // Admin/Notes on the home screen. Switched to green so the four + // default tiles are visually distinct. Existing installs are + // migrated by scripts/src/update-app-colors.ts. + color: "#22c55e", isActive: true, isSystem: false, sortOrder: 7, diff --git a/scripts/src/update-app-colors.ts b/scripts/src/update-app-colors.ts new file mode 100644 index 00000000..5a32f1ce --- /dev/null +++ b/scripts/src/update-app-colors.ts @@ -0,0 +1,48 @@ +import { db, appsTable } from "@workspace/db"; +import { and, eq } from "drizzle-orm"; + +// #603: One-shot migration that bumps the seeded app colours on +// existing installs to the new #603 palette. The seed itself uses +// `onConflictDoNothing` on `slug`, so previously-seeded rows would +// otherwise keep their old hex values forever and continue to render +// as three identical blue tiles on the home screen. +// +// Each update is gated on the row STILL holding the old default — +// admins who customised an app colour from the Apps editor keep their +// choice untouched. +const MIGRATIONS: Array<{ slug: string; from: string; to: string }> = [ + // Meetings: dark navy → green (was falling through to default blue). + { slug: "executive-meetings", from: "#0B1E3F", to: "#22c55e" }, + // Notifications: purple → cyan (purple is no longer in the palette). + { slug: "notifications", from: "#8b5cf6", to: "#06b6d4" }, +]; + +async function main(): Promise { + console.log("Migrating app colours to the #603 palette..."); + let touched = 0; + for (const m of MIGRATIONS) { + const updated = await db + .update(appsTable) + .set({ color: m.to }) + .where(and(eq(appsTable.slug, m.slug), eq(appsTable.color, m.from))) + .returning({ slug: appsTable.slug, color: appsTable.color }); + if (updated.length === 0) { + console.log( + ` - ${m.slug}: skipped (row missing or already customised)`, + ); + } else { + touched += updated.length; + for (const row of updated) { + console.log(` - ${row.slug}: color → ${row.color}`); + } + } + } + console.log(`Done. Rows updated: ${touched}.`); +} + +main() + .catch((err) => { + console.error(err); + process.exit(1); + }) + .finally(() => process.exit(0));