diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb52039..a9e2c04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,7 +116,7 @@ jobs: - run: python -m unittest discover -s tests -v working-directory: dashboard - name: open-access + markup contract tests - run: node --test tests/domains_markup.test.mjs tests/open_access.test.mjs tests/notes_markup.test.mjs tests/review_pr122_drawer.test.mjs tests/flux_markup.test.mjs tests/review_pr125.test.mjs tests/criteria_markup.test.mjs tests/review_pr127_drawer.test.mjs tests/phone_layout.test.mjs + run: node --test tests/domains_markup.test.mjs tests/open_access.test.mjs tests/notes_markup.test.mjs tests/review_pr122_drawer.test.mjs tests/flux_markup.test.mjs tests/review_pr125.test.mjs tests/criteria_markup.test.mjs tests/review_pr127_drawer.test.mjs tests/phone_layout.test.mjs tests/wordmark.test.mjs working-directory: dashboard schema-check: diff --git a/Cargo.lock b/Cargo.lock index 567b7a4..0186ed9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2708,7 +2708,7 @@ dependencies = [ [[package]] name = "ptask-bot" -version = "3.51.4" +version = "3.51.5" dependencies = [ "anyhow", "jiff", @@ -2724,7 +2724,7 @@ dependencies = [ [[package]] name = "ptask-cli" -version = "3.51.4" +version = "3.51.5" dependencies = [ "anyhow", "axum", @@ -2752,7 +2752,7 @@ dependencies = [ [[package]] name = "ptask-core" -version = "3.51.4" +version = "3.51.5" dependencies = [ "hex", "interim", @@ -2773,7 +2773,7 @@ dependencies = [ [[package]] name = "ptask-distill" -version = "3.51.4" +version = "3.51.5" dependencies = [ "anyhow", "candle-core", @@ -2797,7 +2797,7 @@ dependencies = [ [[package]] name = "ptask-notify" -version = "3.51.4" +version = "3.51.5" dependencies = [ "lettre", "ptask-core", @@ -2810,7 +2810,7 @@ dependencies = [ [[package]] name = "ptask-server" -version = "3.51.4" +version = "3.51.5" dependencies = [ "anyhow", "axum", @@ -2842,7 +2842,7 @@ dependencies = [ [[package]] name = "ptask-tui" -version = "3.51.4" +version = "3.51.5" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 53f38ae..d7b5550 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ resolver = "2" [workspace.package] -version = "3.51.4" +version = "3.51.5" edition = "2024" license-file = "LICENSE" authors = ["PureTensor, Inc. "] @@ -52,12 +52,12 @@ rmcp = { version = "3.3.0", features = ["server", "macros", "schemars", "transpo futures-util = { version = "0.3", default-features = false } # Internal crates -ptask-core = { path = "crates/ptask-core", version = "3.51.4" } -ptask-tui = { path = "crates/ptask-tui", version = "3.51.4" } -ptask-server = { path = "crates/ptask-server", version = "3.51.4" } -ptask-bot = { path = "crates/ptask-bot", version = "3.51.4" } -ptask-distill = { path = "crates/ptask-distill", version = "3.51.4" } -ptask-notify = { path = "crates/ptask-notify", version = "3.51.4" } +ptask-core = { path = "crates/ptask-core", version = "3.51.5" } +ptask-tui = { path = "crates/ptask-tui", version = "3.51.5" } +ptask-server = { path = "crates/ptask-server", version = "3.51.5" } +ptask-bot = { path = "crates/ptask-bot", version = "3.51.5" } +ptask-distill = { path = "crates/ptask-distill", version = "3.51.5" } +ptask-notify = { path = "crates/ptask-notify", version = "3.51.5" } # HMAC for outbound webhook signing (v0.3.5). sha2 = "0.10" diff --git a/crates/ptask-core/src/config.rs b/crates/ptask-core/src/config.rs index 33d5f6f..8572fb0 100644 --- a/crates/ptask-core/src/config.rs +++ b/crates/ptask-core/src/config.rs @@ -75,7 +75,7 @@ pub struct DashConfig { /// host of `$PTASK_DASH_URL`, and this machine's own short hostname. /// Lower-cased, without port or trailing dot. pub allowed_hosts: Vec, - /// Brand shown in the cockpit (`$PTASK_DASH_TITLE`, default "PTASK"). + /// Brand shown in the cockpit (`$PTASK_DASH_TITLE`, default "pTASK"). pub title: String, /// Domain hats (`$PTASK_DASH_DOMAINS`). Empty = legacy ENG/MGMT mode. pub domains: Vec, @@ -286,7 +286,7 @@ impl Config { frame_ancestor: env_nonempty("PTASK_DASH_FRAME_ANCESTOR"), url: env_nonempty("PTASK_DASH_URL"), allowed_hosts, - title: env_nonempty("PTASK_DASH_TITLE").unwrap_or_else(|| "PTASK".into()), + title: env_nonempty("PTASK_DASH_TITLE").unwrap_or_else(|| "pTASK".into()), domains, default_domain, }, diff --git a/dashboard/README.md b/dashboard/README.md index c8b8fe9..d583efc 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -278,6 +278,12 @@ The canonical `pt serve` and `tasks.db` are never modified — nothing to revert ## Version +- **v0.26.2** (pt 3.51.5) — The brand is now **pTASK**, styled like pCIRCUIT + and pNOC: a white lowercase "p", then the name in capitals in the shared + wordmark blue (#40c4ff). Covers the header, the page title and the + home-screen app name. `PTASK_DASH_TITLE` (sidecar and `pt serve`) now + defaults to `pTASK`; a configured brand in the same "p" + capitals form gets + the same treatment, and any other brand stays plain text. - **v0.26.1** (pt 3.51.4) — iPhone 18 Pro Max home-screen app fit, checked in the iOS 27 simulator (440×956, 62px status-bar inset). A cold launch no longer leaves the board under a doubled 124px gap (iOS parks the page at diff --git a/dashboard/server.py b/dashboard/server.py index 0c162bb..b21f6e6 100644 --- a/dashboard/server.py +++ b/dashboard/server.py @@ -63,7 +63,7 @@ PTASK_DB SQLite path (default ~/puretensor-tasks/tasks.db) PTASK_BIN pt binary (default ~/.cargo/bin/pt) PTASK_DASH_BIND bind addr (default 127.0.0.1:9510) - PTASK_DASH_TITLE dashboard brand (default "PTASK") + PTASK_DASH_TITLE dashboard brand (default "pTASK") PTASK_DASH_DOMAINS comma-separated key[:Label[:ABBR]] domain list PTASK_DASH_DEFAULT_DOMAIN configured key used for unlabelled tasks PTASK_DASH_WWW static dir (default ./www next to this file) @@ -147,7 +147,7 @@ def resolve_default_domain(domains: list[dict], requested: str | None) -> str | # the dashboard exposes the same task data. Production sets PTASK_DASH_BIND. BIND = os.environ.get("PTASK_DASH_BIND", "127.0.0.1:9510") -VERSION = "0.26.1" +VERSION = "0.26.2" def _allowed_host_entry(entry: str) -> str: @@ -172,7 +172,7 @@ def _allowed_host_entry(entry: str) -> str: # or aws child inherits it. DECIDE_TOKEN_MIN = 16 DECIDE_TOKEN = os.environ.pop("PTASK_DASH_DECIDE_TOKEN", "").strip() -DASH_TITLE = os.environ.get("PTASK_DASH_TITLE", "PTASK") +DASH_TITLE = os.environ.get("PTASK_DASH_TITLE", "pTASK") DASH_DOMAINS = parse_domains(os.environ.get("PTASK_DASH_DOMAINS")) DASH_DEFAULT_DOMAIN = resolve_default_domain( DASH_DOMAINS, os.environ.get("PTASK_DASH_DEFAULT_DOMAIN"), diff --git a/dashboard/tests/wordmark.test.mjs b/dashboard/tests/wordmark.test.mjs new file mode 100644 index 0000000..8e3009a --- /dev/null +++ b/dashboard/tests/wordmark.test.mjs @@ -0,0 +1,62 @@ +// pTASK wordmark (dashboard 0.26.2): the brand reads like pCIRCUIT and pNOC, +// a white lowercase "p" then the name in capitals in the wordmark blue. Runs +// the shipped renderBrand against a fake element, and pins the markup and CSS. + +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; + +const shell = readFileSync(new URL("../www/index.html", import.meta.url), "utf8"); + +const src = shell.match(/\nfunction renderBrand\(el,title\)\{[\s\S]*?\n\}\n/); +assert.ok(src, "renderBrand is in index.html"); + +function render(title) { + const doc = { createElement: () => ({ className: "", textContent: "" }) }; + const el = { + textContent: "", + children: null, + replaceChildren(...nodes) { this.children = nodes; this.textContent = ""; }, + }; + new Function("document", `${src[0]}; return renderBrand;`)(doc)(el, title); + return el; +} + +test("pTASK renders as a plain p and a blue TASK", () => { + const el = render("pTASK"); + assert.equal(el.children.length, 2); + assert.equal(el.children[0], "p"); + assert.equal(el.children[1].className, "wordmark-name"); + assert.equal(el.children[1].textContent, "TASK"); +}); + +test("another tenant's brand stays plain text", () => { + for (const title of ["ALAN", "PTASK", "pt", "pure"]) { + const el = render(title); + assert.equal(el.children, null, title); + assert.equal(el.textContent, title); + } +}); + +test("the shell ships the wordmark before config arrives, and defaults to pTASK", () => { + assert.match(shell, /pTASK<\/span><\/b>/); + assert.match(shell, /cfg\.title:'pTASK'/); + assert.match(shell, //); + assert.match(shell, /pTASK · Triage Cockpit<\/title>/); +}); + +test("the name is in the shared wordmark blue, the p in white", () => { + assert.match(shell, /--wordmark:#40c4ff;/); + assert.match(shell, /\.brandtxt b\{[^}]*color:#fff[^}]*\}/); + assert.match(shell, /\.brandtxt b \.wordmark-name\{color:var\(--wordmark\)\}/); +}); + +test("the wordmark is in pCIRCUIT's system sans, not the mission theme's mono", () => { + assert.match(shell, /--font-wordmark:-apple-system,BlinkMacSystemFont,"SF Pro Text"/); + assert.match(shell, /\.brandtxt b\{font-family:var\(--font-wordmark\)/); +}); + +test("the manifest names the home-screen app pTASK", () => { + const manifest = JSON.parse(readFileSync(new URL("../www/manifest.webmanifest", import.meta.url), "utf8")); + assert.equal(manifest.short_name, "pTASK"); +}); diff --git a/dashboard/www/index.html b/dashboard/www/index.html index 9acf649..c90e1b8 100644 --- a/dashboard/www/index.html +++ b/dashboard/www/index.html @@ -9,10 +9,10 @@ <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> -<meta name="apple-mobile-web-app-title" content="PTASK"> +<meta name="apple-mobile-web-app-title" content="pTASK"> <link rel="apple-touch-icon" href="/apple-touch-icon.png"> <link rel="manifest" href="/manifest.webmanifest"> -<title>PTASK · Triage Cockpit +pTASK · Triage Cockpit