From 7e664a11ff33c59de808d07576351cd196be5fe7 Mon Sep 17 00:00:00 2001 From: DanMat Date: Tue, 21 Jul 2026 19:54:32 -0400 Subject: [PATCH] fix(web): honor option applicability, and stop the command resizing the page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs in the web configurator, reported from use. Picking a service framework did nothing. The command only emits --server when the service target is selected, which is correct — the flag is inert otherwise — but the control was rendered unconditionally, so clicking "Express" changed neither the command nor the files and looked broken. Options can already declare when they apply; nothing honored it. The web configurator now does, so a control that cannot affect the output isn't offered. That also fixes "Monorepo layout", added in 2.9.0 with a `when` that was quietly dead. The CLI wizard already guarded both by hand. The command box also grew the page. Grid items default to min-width:auto, so an unwrapped 300-character command set the column's minimum width: measured in-page, the columns went from an even 720/720 to 48/1534 with a 1582px scroll. min-width:0 lets them hold their share and lets the existing overflow-x on .cmd actually scroll. Verified in the browser: selecting the service target reveals the framework picker and Express then yields `--server express`; columns stay 720/720 whether the command is 194 or 303 characters. Co-Authored-By: Claude Opus 4.8 --- docs/app.js | 12 +++++++++++- docs/index.html | 4 ++++ docs/packkit-core.js | 1 + src/core/options.js | 1 + test/core.test.js | 14 +++++++++++++- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/app.js b/docs/app.js index de17f12..fb3a689 100644 --- a/docs/app.js +++ b/docs/app.js @@ -18,7 +18,9 @@ function renderForm() { const form = $('#form'); form.innerHTML = ''; for (const group of GROUPS) { - const keys = Object.keys(OPTIONS).filter((k) => OPTIONS[k].group === group.id && !HIDDEN.has(k)); + const keys = Object.keys(OPTIONS).filter( + (k) => OPTIONS[k].group === group.id && !HIDDEN.has(k) && applies(k, state), + ); if (!keys.length) continue; const wrap = el('div', { className: 'group' }, el('h3', { textContent: group.label })); for (const key of keys) wrap.append(renderField(key)); @@ -26,6 +28,14 @@ function renderForm() { } } +// An option that doesn't apply to the current config (a service framework with +// no service target, a monorepo layout with no monorepo) is hidden rather than +// shown-and-ignored: changing it would alter neither the command nor the files. +function applies(key, cfg) { + const when = OPTIONS[key].when; + return typeof when === 'function' ? !!when(cfg) : true; +} + function renderField(key) { const opt = OPTIONS[key]; const field = el('div', { className: 'field' }, el('label', { textContent: opt.label })); diff --git a/docs/index.html b/docs/index.html index e42900a..b54ab9a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -29,6 +29,10 @@ .links a:hover { border-color: var(--accent); } main { display: grid; grid-template-columns: 1fr 1fr; gap: 0; min-height: calc(100vh - 61px); } + /* Grid items default to min-width:auto, so a long unwrapped command sets the + column's minimum width and the whole panel grows with it. min-width:0 lets + the columns hold their share and the overflow-x below actually scroll. */ + main > *, .split > * { min-width: 0; } @media (max-width: 900px) { main { grid-template-columns: 1fr; } } .config { padding: 22px 24px; overflow-y: auto; } diff --git a/docs/packkit-core.js b/docs/packkit-core.js index d587ede..0f16331 100644 --- a/docs/packkit-core.js +++ b/docs/packkit-core.js @@ -47,6 +47,7 @@ var OPTIONS = { type: "select", label: "Service framework (HTTP service)", default: "hono", + when: (cfg) => cfg.target?.includes("service"), choices: [ { value: "hono", label: "Hono (fast, web-standard)" }, { value: "fastify", label: "Fastify" }, diff --git a/src/core/options.js b/src/core/options.js index eaacdf2..bdb8e26 100644 --- a/src/core/options.js +++ b/src/core/options.js @@ -36,6 +36,7 @@ export const OPTIONS = { }, serviceFramework: { group: 'core', type: 'select', label: 'Service framework (HTTP service)', default: 'hono', + when: (cfg) => cfg.target?.includes('service'), choices: [ { value: 'hono', label: 'Hono (fast, web-standard)' }, { value: 'fastify', label: 'Fastify' }, diff --git a/test/core.test.js b/test/core.test.js index 5b4c65b..aa66e53 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -1,6 +1,6 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { generate, fromPreset, normalizeConfig, PRESETS, PRESET_INFO } from '../src/core/index.js'; +import { generate, fromPreset, normalizeConfig, PRESETS, PRESET_INFO, OPTIONS } from '../src/core/index.js'; test('node-service: Hono app, Dockerfile, private, start script', () => { const out = generate(fromPreset('node-service', { name: 'svc' })); @@ -371,3 +371,15 @@ test('packkit.json is deterministic — same config, same bytes', () => { generate(fromPreset('ts-lib', cfg)).files['packkit.json'], ); }); + +test('options that do not apply declare it, so surfaces can hide them', () => { + // The web configurator showed these regardless, so changing them altered + // neither the command nor the generated files — which reads as a broken app. + assert.equal(typeof OPTIONS.serviceFramework.when, 'function'); + assert.equal(OPTIONS.serviceFramework.when({ target: ['library'] }), false); + assert.equal(OPTIONS.serviceFramework.when({ target: ['library', 'service'] }), true); + + assert.equal(typeof OPTIONS.monorepoLayout.when, 'function'); + assert.equal(OPTIONS.monorepoLayout.when({ monorepo: false }), false); + assert.equal(OPTIONS.monorepoLayout.when({ monorepo: true }), true); +});