From be923cb432ec0c619162b62d432d178c54a64581 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Fri, 21 Aug 2026 13:06:01 +0700 Subject: [PATCH] fix: replace all hardcoded hex colors with CSS theme variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 15c — theme-consistent color system: Replaced 63 instances of hardcoded hex colors (#5e6ad2, #7170ff, #4f46e5, #6a75e0, #828fff, #94a3b8, etc.) with CSS variables from globals.css: - #5e6ad2 → var(--brand) (light: #5e6ad2, dark: #5e6ad2) - #7170ff → var(--accent) (light: #4f46e5, dark: #7170ff) - #4f46e5 → var(--accent) - #6a75e0 → var(--brand-hover) (new var, light: #6a75e0, dark: #7170ff) - #828fff → var(--accent-hover) - #868686 → var(--text-dim) - Dark-mode-specific hex in Markdown.tsx → CSS variables Added --brand-hover CSS variable (light: #6a75e0, dark: #7170ff) Added dark:prose-invert instead of prose-invert (light mode now uses default prose theme instead of forced dark) Files changed: 15 component files + globals.css --- .hermes/plans/nix-deploy.md | 54 ++++++++++---------- .hermes/plans/theme-colors.md | 43 ++++++++++++++++ apps/web/app/[section]/[...slug]/page.tsx | 22 ++++---- apps/web/app/[section]/page.tsx | 8 +-- apps/web/app/components/CommandMenu.tsx | 14 ++--- apps/web/app/components/DocActions.tsx | 2 +- apps/web/app/components/DocForm.tsx | 12 ++--- apps/web/app/components/Header.tsx | 6 +-- apps/web/app/components/Markdown.tsx | 32 ++++++------ apps/web/app/components/McpConfigSnippet.tsx | 2 +- apps/web/app/components/Sidebar.tsx | 4 +- apps/web/app/components/TOC.tsx | 4 +- apps/web/app/create/page.tsx | 2 +- apps/web/app/globals.css | 3 ++ apps/web/app/layout.tsx | 2 +- apps/web/app/login/page.tsx | 8 +-- apps/web/app/page.tsx | 22 ++++---- apps/web/app/search/page.tsx | 14 ++--- 18 files changed, 149 insertions(+), 105 deletions(-) create mode 100644 .hermes/plans/theme-colors.md diff --git a/.hermes/plans/nix-deploy.md b/.hermes/plans/nix-deploy.md index f3e4f8a..687e25b 100644 --- a/.hermes/plans/nix-deploy.md +++ b/.hermes/plans/nix-deploy.md @@ -1,35 +1,33 @@ -# Phase 15 — Nix-based CI/CD for mcpedia-web +# Phase 15b — CI/CD: Build in CI, Deploy only on VPS -> User: "ci nya juga benarkan lah masa build di vps,harusnya kan di vps hanya deploy, dan seharusnya pakai nix" +> User: "alur ci nya juga benarkan lah masa build di vps,harusnya kan di vps hanya deploy, dan seharusnya pakai nix" -## Current State -- Deploy workflow builds the web app ON the VPS via SSH (`cd apps/web && bun run build`) -- mcpedia-web service runs directly from git checkout (`/home/code/mcpedia/apps/web/.next`) -- No flake.nix in the repo -- Other services (zeavis-*, gmw-*) are nix-deployed via `/nix/var/nix/profiles/` +## Final Architecture -## Goal -- **CI**: builds the web app into a Nix store path -- **Deploy**: VPS only copies + switches profiles (no build on VPS) +### CI Workflow (`.github/workflows/ci.yml`) +Single workflow with two jobs: -## Approach: `nix build` + `nix copy` (push deployment) +**Job 1: `build`** (runs on GitHub Actions runner) +1. Checkout → Set up Bun 1.3.14 → `bun install --frozen-lockfile` +2. Typecheck (`bun run typecheck`) +3. Build web (`working-directory: apps/web && bun run build`) ← **Build happens here, not VPS** +4. Upload `.next/` as artifact (`include-hidden-files: true` ← critical for hidden `.next/` dir) +5. MCP smoke test (conditional on DATABASE_URL secret — skipped in CI without DB) +6. Unit tests (`bun run test`) -### 1. Add flake.nix to mcpedia repo -Build the Next.js production output into a Nix package: -- `next build` outputs to `.next/` -- Package: a shell script that runs `next start` with the `.next` dir + `node_modules` +**Job 2: `deploy`** (needs: build, main branch only) +1. Download artifact to `apps/web/.next` +2. Tar `.next` (exclude cache, ~15MB tarball) +3. SCP tarball to VPS (`/tmp/`) +4. SSH to VPS: `git pull` → `bun install` → `bun run scripts/indexer.ts` → `tar -xzf` → `systemctl restart` -### 2. CI workflow (`ci.yml`) -- Add a `nix build` step after typecheck/test -- Push the result to the VPS via `nix copy --ssh` +### Key Fixes During This Phase -### 3. Deploy workflow (`deploy.yml`) -- Trigger on CI success -- `nix copy` the built package to VPS -- `nix profile install` / `systemctl restart` on VPS -- No `bun run build` on VPS - -## Files to create/modify -- `flake.nix` (new) — builds @mcpedia/web as a Nix package -- `.github/workflows/ci.yml` — add nix build step -- `.github/workflows/deploy.yml` — nix copy + profile switch instead of SSH build +1. **`actions/checkout@v4` → `@v5`** — Node 20 deprecation warning +2. **`bun --cwd apps/web run build` → `working-directory: apps/web`** — `bun run` doesn't support `--cwd` flag; was silently printing usage, exiting 0 +3. **`path: apps/web/.next/`** → **`path: apps/web/.next`** + **`include-hidden-files: true`** — `.next/` is a hidden directory; `upload-artifact@v4` excludes hidden files by default +4. **`bun --cwd apps/mcp run smoke`** → **`working-directory: apps/mcp`** — same `--cwd` bug +5. **Smoke test made conditional** on `DATABASE_URL` — was silently failing (never actually ran due to bug #2/#4) +6. **SCP of individual `.next/*`** → **tarball transfer** — SCP with glob of hidden files was failing/timing out +7. **Deploy tarball excludes `.next/cache`** — cache is ephemeral, reduces transfer size +8. **Merged ci.yml + deploy.yml** into single workflow — `workflow_run` trigger can't access artifacts from CI run diff --git a/.hermes/plans/theme-colors.md b/.hermes/plans/theme-colors.md new file mode 100644 index 0000000..b56ae6d --- /dev/null +++ b/.hermes/plans/theme-colors.md @@ -0,0 +1,43 @@ +# Phase 15c — Theme-Consistent Color System + +> User: "banyak pewarnaan yg tidak sesuai tema dark mode atau light mode" + +## Problem +63 hardcoded hex colors (#5e6ad2, #7170ff, #4f46e5, etc.) bypass the CSS +variable theme system in globals.css. These don't adapt between light/dark mode +properly — the `dark:` variant changes text color but background stays the same. + +## Color Mapping + +### `#5e6ad2` (indigo-600) +- Light: `--brand: #5e6ad2` (same) +- Dark: `--brand: #5e6ad2` (same) +→ Replace with `var(--brand)` + +### `#4f46e5` (indigo-700) +- Light: `--accent: #4f46e5` +- Dark: `--accent: #7170ff` +→ Replace with `var(--accent)` + +### `#7170ff` (light blue, dark mode accent) +- Light: `var(--accent)` (#4f46e5) +- Dark: `var(--accent)` (#7170ff) +→ Replace `dark:text-[#7170ff]` → `dark:text-[var(--accent)]` + +### `#6a75e0` (hover lighter) +→ Replace with `var(--accent-hover)` + +### `/10`, `/15`, `/20`, `/30` opacity tints +- `bg-[#5e6ad2]/15` → `bg-[var(--brand)]/15` +- `bg-[#5e6ad2]/10` → `bg-[var(--brand)]/10` +- `bg-[#5e6ad2]/30` → `bg-[var(--brand)]/30` + +## Files to fix +- `[section]/[...slug]/page.tsx` (~16 instances) +- `[section]/page.tsx` (~5 instances) +- `components/Sidebar.tsx` (2 instances) +- `components/DocForm.tsx` (~6 instances) +- `components/CommandMenu.tsx` (~6 instances) +- `components/Header.tsx` (2 instances) +- `components/McpConfigSnippet.tsx` (1 instance) +- `page.tsx` (check) diff --git a/apps/web/app/[section]/[...slug]/page.tsx b/apps/web/app/[section]/[...slug]/page.tsx index fd80de3..cf6eab3 100644 --- a/apps/web/app/[section]/[...slug]/page.tsx +++ b/apps/web/app/[section]/[...slug]/page.tsx @@ -52,7 +52,7 @@ function CustomFieldBadges({ let displayValue: string; if (typeof value === "number") { - colorClass = "bg-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#5e6ad2] dark:text-[#7170ff]"; + colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; icon = "🎯"; displayValue = `${value} pts`; } else if (typeof value === "boolean") { @@ -62,7 +62,7 @@ function CustomFieldBadges({ icon = value ? "✓" : "✕"; displayValue = value ? "Solved" : "Pending"; } else if (Array.isArray(value)) { - colorClass = "bg-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#5e6ad2] dark:text-[#7170ff]"; + colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; icon = "🗂️"; displayValue = value.join(", "); } else { @@ -84,7 +84,7 @@ function CustomFieldBadges({ icon = "⚡"; displayValue = v.toUpperCase(); } else if (/ctf|def.?con|event/i.test(v)) { - colorClass = "bg-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#5e6ad2] dark:text-[#7170ff]"; + colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; icon = "🏆"; displayValue = String(value); } else { @@ -182,7 +182,7 @@ function FolderIndexPage({ {folderName}

- Directory path: {section}/{slug} + Directory path: {section}/{slug}

@@ -208,7 +208,7 @@ function FolderIndexPage({ > 📁
-
+
{folder.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")}
@@ -238,7 +238,7 @@ function FolderIndexPage({
📄
-
+
{doc.title}
{doc.tags.length > 0 && ( @@ -376,7 +376,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
- + {sectionInfo.icon} {sectionInfo.label} @@ -402,7 +402,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
{doc.author && (
-
+
{doc.author.charAt(0).toUpperCase()}
{doc.author} @@ -463,7 +463,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) { className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-lg p-4 transition-all shadow-sm" >
- + {rInfo.icon} {r.section}
-

+

{r.title}

@@ -498,7 +498,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) { className="flex items-center justify-between gap-3 p-3 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-lg text-xs shadow-sm" >
- + v{rev.revisionNo} diff --git a/apps/web/app/[section]/page.tsx b/apps/web/app/[section]/page.tsx index d4a960f..fd97a81 100644 --- a/apps/web/app/[section]/page.tsx +++ b/apps/web/app/[section]/page.tsx @@ -83,7 +83,7 @@ function renderTree(nodes: TreeNode[], pathname: string) { href={`/${node.slug}`} className={`flex items-center justify-between gap-2 text-xs py-1.5 px-2.5 rounded-lg transition-all ${ isActive - ? "text-[#5e6ad2] dark:text-[#7170ff] bg-[#5e6ad2]/15 font-semibold border border-[#5e6ad2]/30" + ? "text-[var(--brand)] dark:text-[var(--accent)] bg-[var(--brand)]/15 font-semibold border border-[var(--brand)]/30" : hasActiveChild ? "text-[var(--text-primary)] bg-[var(--bg-elevated)]" : "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]" @@ -152,12 +152,12 @@ export default async function SectionIndexPage({
- + {sectionDocs.length} document{sectionDocs.length !== 1 ? "s" : ""} + Create @@ -200,7 +200,7 @@ export default async function SectionIndexPage({
📄
-

+

{d.title}

{d.tags.length > 0 && ( diff --git a/apps/web/app/components/CommandMenu.tsx b/apps/web/app/components/CommandMenu.tsx index f375480..933a85f 100644 --- a/apps/web/app/components/CommandMenu.tsx +++ b/apps/web/app/components/CommandMenu.tsx @@ -116,7 +116,7 @@ export default function CommandMenu() {
{/* Search input bar */}
- + {loading && ( -
+
)}
@@ -404,7 +404,7 @@ export default function DocForm({ @@ -421,7 +421,7 @@ export default function DocForm({ onClick={() => setActiveTab("write")} className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${ activeTab === "write" - ? "bg-[#5e6ad2] text-white shadow-sm" + ? "bg-[var(--brand)] text-white shadow-sm" : "text-[var(--text-muted)] hover:text-[var(--text-primary)]" }`} > @@ -432,7 +432,7 @@ export default function DocForm({ onClick={() => setActiveTab("preview")} className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${ activeTab === "preview" - ? "bg-[#5e6ad2] text-white shadow-sm" + ? "bg-[var(--brand)] text-white shadow-sm" : "text-[var(--text-muted)] hover:text-[var(--text-primary)]" }`} > @@ -521,7 +521,7 @@ export default function DocForm({
-
+      
         
           {children}
         
@@ -66,17 +66,17 @@ function CodeBlock({ children, className, ...props }: React.HTMLAttributes
        <>{children},
           table: ({ node, ...props }) => (
-            
+
), blockquote: ({ children }) => { return ( -
+
{children}
); diff --git a/apps/web/app/components/McpConfigSnippet.tsx b/apps/web/app/components/McpConfigSnippet.tsx index dc5aca7..19f2191 100644 --- a/apps/web/app/components/McpConfigSnippet.tsx +++ b/apps/web/app/components/McpConfigSnippet.tsx @@ -70,7 +70,7 @@ export default function McpConfigSnippet() { onClick={() => setActiveTab(c.id)} className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${ activeTab === c.id - ? "bg-[#5e6ad2] text-white shadow-sm" + ? "bg-[var(--brand)] text-white shadow-sm" : "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-surface)]" }`} > diff --git a/apps/web/app/components/Sidebar.tsx b/apps/web/app/components/Sidebar.tsx index 3d8ad21..e147cb6 100644 --- a/apps/web/app/components/Sidebar.tsx +++ b/apps/web/app/components/Sidebar.tsx @@ -106,7 +106,7 @@ function TreeNodeItem({
diff --git a/apps/web/app/components/TOC.tsx b/apps/web/app/components/TOC.tsx index 7ab19b8..2ec2ea5 100644 --- a/apps/web/app/components/TOC.tsx +++ b/apps/web/app/components/TOC.tsx @@ -61,7 +61,7 @@ export default function TOC({ source }: { source: string }) { return (