From 225fd77103fa505dfc620b7ab35296d06f06b423 Mon Sep 17 00:00:00 2001 From: Mufacoderz Date: Sun, 14 Jun 2026 15:05:09 +0800 Subject: [PATCH] Improve responsive welcome logo panel Closes #9 Co-authored-by: devmap-agent <238585242+devmap-agent@users.noreply.github.com> --- docs/design.md | 10 +++-- docs/for-me-personal/PROGRESS.md | 11 ++++++ docs/for-me-personal/TEST.md | 4 +- packages/cli/src/utils/welcome.ts | 64 ++++++++++++++++++++++++++----- packages/cli/test/welcome.test.ts | 29 ++++++++++++++ 5 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 packages/cli/test/welcome.test.ts diff --git a/docs/design.md b/docs/design.md index 4bcb88d..34bd040 100644 --- a/docs/design.md +++ b/docs/design.md @@ -142,9 +142,13 @@ DEVMAP ANALYSIS ENGINE INITIALIZATION STARTED The default `devmap` command may use the large DevMap ASCII wordmark as the primary brand signal. -The wordmark should use the aqua brand color. - -Everything below the wordmark should stay concise. +The wordmark should use the aqua brand color. + +Everything below the wordmark should stay concise. + +The welcome brand signal is framed in a single aqua panel. Wide terminals show +the DevMap symbol above the large wordmark. Narrow terminals keep the symbol +but use a compact `DEVMAP` title so the panel does not wrap or clip. Example: diff --git a/docs/for-me-personal/PROGRESS.md b/docs/for-me-personal/PROGRESS.md index 8eee096..dbd76a8 100644 --- a/docs/for-me-personal/PROGRESS.md +++ b/docs/for-me-personal/PROGRESS.md @@ -19,6 +19,17 @@ Terakhir diperbarui: 2026-06-14 sama-sama menghitung hasil scanner setelah ignore filtering. - Automated test saat ini berjumlah 47 dan seluruhnya lulus. +### Welcome Brand Panel + +- Welcome screen sekarang menampilkan simbol logo DevMap di atas ASCII + wordmark. +- Logo, wordmark, dan border memakai warna aqua brand yang sama. +- Terminal lebar memakai wordmark penuh dalam panel maksimal 76 kolom. +- Terminal sempit memakai judul `DEVMAP` ringkas agar panel tidak wrap atau + terpotong. +- Renderer panel dipisahkan agar layout lebar dan sempit dapat diuji langsung. +- Automated test saat ini berjumlah 49 dan seluruhnya lulus. + ## Update 2026-06-13 ### Packed CLI End-to-End diff --git a/docs/for-me-personal/TEST.md b/docs/for-me-personal/TEST.md index 89f923f..b4b2359 100644 --- a/docs/for-me-personal/TEST.md +++ b/docs/for-me-personal/TEST.md @@ -153,8 +153,8 @@ pnpm --filter devmap test:types Hasil minimum saat ini: ```text -tests 47 -pass 47 +tests 49 +pass 49 fail 0 ``` diff --git a/packages/cli/src/utils/welcome.ts b/packages/cli/src/utils/welcome.ts index 76c2684..94a354d 100644 --- a/packages/cli/src/utils/welcome.ts +++ b/packages/cli/src/utils/welcome.ts @@ -2,20 +2,36 @@ import { existsSync } from "node:fs"; import { getSnapshotPath } from "../cache/snapshot.js"; import { theme } from "./output.js"; -const LOGO = String.raw` - ██████╗ ███████╗██╗ ██╗███╗ ███╗ █████╗ ██████╗ - ██╔══██╗██╔════╝██║ ██║████╗ ████║██╔══██╗██╔══██╗ - ██║ ██║█████╗ ██║ ██║██╔████╔██║███████║██████╔╝ - ██║ ██║██╔══╝ ╚██╗ ██╔╝██║╚██╔╝██║██╔══██║██╔═══╝ - ██████╔╝███████╗ ╚████╔╝ ██║ ╚═╝ ██║██║ ██║██║ - ╚═════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ -`; +const SYMBOL_LOGO = [ + " ╷ ╱╲", + " ╭──────┼──────╱ ╲", + " ╭─╯ ●─────╯ ╱", + " ╱ ╱│ ╱", + "│ ╱ │ ╭────╯", + "│ ╱ │╱╱", + " ╲__╱ ╱│", + " ╲____╱ │", + " ╵" +]; + +const WIDE_WORDMARK = [ + "██████╗ ███████╗██╗ ██╗███╗ ███╗ █████╗ ██████╗", + "██╔══██╗██╔════╝██║ ██║████╗ ████║██╔══██╗██╔══██╗", + "██║ ██║█████╗ ██║ ██║██╔████╔██║███████║██████╔╝", + "██║ ██║██╔══╝ ╚██╗ ██╔╝██║╚██╔╝██║██╔══██║██╔═══╝", + "██████╔╝███████╗ ╚████╔╝ ██║ ╚═╝ ██║██║ ██║██║", + "╚═════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝" +]; + +const WIDE_PANEL_WIDTH = 76; +const COMPACT_PANEL_WIDTH = 48; +const WIDE_TERMINAL_MINIMUM = 72; export function printWelcome(projectRoot: string): void { const hasSnapshot = existsSync(getSnapshotPath(projectRoot)); const status = hasSnapshot ? "Project snapshot found." : "No project analyzed yet."; - console.log(`${theme.aqua}${LOGO}${theme.reset}`); + console.log(renderWelcomeBrandPanel(process.stdout.columns ?? 80)); console.log(" Understand Any Codebase."); console.log(`\n ${theme.gray}──────────────────────────────────────────${theme.reset}\n`); console.log(` ${status}\n`); @@ -31,6 +47,36 @@ export function printWelcome(projectRoot: string): void { console.log(""); } +export function renderWelcomeBrandPanel(terminalWidth: number): string { + const isWide = terminalWidth >= WIDE_TERMINAL_MINIMUM; + const panelWidth = Math.max( + 28, + Math.min( + terminalWidth, + isWide ? WIDE_PANEL_WIDTH : COMPACT_PANEL_WIDTH + ) + ); + const contentWidth = panelWidth - 4; + const wordmark = isWide ? WIDE_WORDMARK : ["DEVMAP"]; + const content = [ + ...SYMBOL_LOGO, + "", + ...wordmark + ]; + const top = `╭${"─".repeat(panelWidth - 2)}╮`; + const bottom = `╰${"─".repeat(panelWidth - 2)}╯`; + const rows = content.map((line) => { + const visibleLine = line.slice(0, contentWidth); + const leftPadding = Math.floor((contentWidth - visibleLine.length) / 2); + const rightPadding = contentWidth - visibleLine.length - leftPadding; + return `│ ${" ".repeat(leftPadding)}${visibleLine}${" ".repeat(rightPadding)} │`; + }); + + return [top, ...rows, bottom] + .map((line) => `${theme.aqua}${line}${theme.reset}`) + .join("\n"); +} + function printCommand(command: string, description?: string): void { const padded = command.padEnd(22); const suffix = description ? ` ${theme.gray}${description}${theme.reset}` : ""; diff --git a/packages/cli/test/welcome.test.ts b/packages/cli/test/welcome.test.ts new file mode 100644 index 0000000..588660c --- /dev/null +++ b/packages/cli/test/welcome.test.ts @@ -0,0 +1,29 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { renderWelcomeBrandPanel } from "../src/utils/welcome.js"; + +test("welcome brand panel frames the logo and wordmark on wide terminals", () => { + const panel = stripAnsi(renderWelcomeBrandPanel(100)); + const lines = panel.split("\n"); + + assert.match(panel, /██████╗ ███████╗/); + assert.match(panel, /╱╲/); + assert.ok(lines.every((line) => line.length === lines[0]?.length)); + assert.ok((lines[0]?.length ?? 0) <= 76); + assert.match(lines[0] ?? "", /^╭─+╮$/); + assert.match(lines.at(-1) ?? "", /^╰─+╯$/); +}); + +test("welcome brand panel uses a compact wordmark on narrow terminals", () => { + const panel = stripAnsi(renderWelcomeBrandPanel(48)); + const lines = panel.split("\n"); + + assert.match(panel, /DEVMAP/); + assert.ok(lines.every((line) => line.length === lines[0]?.length)); + assert.ok((lines[0]?.length ?? 0) <= 48); + assert.doesNotMatch(panel, /██████/); +}); + +function stripAnsi(value: string): string { + return value.replace(/\x1b\[[0-9;]*m/g, ""); +}