Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
# Runs the test suite on every pull request and on pushes to master.
#
# Two jobs, split by cost. Roughly a third of the suite drives a real browser,
# and downloading Chrome dominates that job's runtime — so the Node-only tests
# run on their own and report in a couple of minutes, which is the feedback
# that matters for almost every change. See docs/specs/CI-SPEC.md.
#
# No secrets are needed. The tests that would use an LLM API key skip when one
# is absent, so this stays useful on pull requests from forks.
name: CI

on:
pull_request:
push:
branches: [master]

# A new push supersedes the previous run on the same branch.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
fast:
name: build + tests (node ${{ matrix.node }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
# The lower bound is what package.json's `engines` field claims, so the
# claim stays honest; the upper is what development actually runs on.
node: ['22', '24']
env:
# This job never launches a browser, and Chrome is ~150MB of the install.
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
# `tsc` with noEmit off is the type check — it is the same compile the
# published package depends on, so a separate lint step would be noise.
- run: npm run build
- run: npm run test:fast

browser:
name: browser tests
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
# `puppeteer` (not puppeteer-core) is a dependency, so its postinstall
# fetches the pinned Chrome build here. Cached across runs by version, so
# only a Puppeteer bump pays the download again.
- name: Cache Puppeteer's Chrome
uses: actions/cache@v4
with:
path: ~/.cache/puppeteer
key: puppeteer-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- run: npm ci
- run: npm run build
- run: npm run test:full
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# framesmith

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Release](https://img.shields.io/github/v/release/vicmaster/framesmith)](https://github.com/vicmaster/framesmith/releases) [![MCP](https://img.shields.io/badge/MCP-compatible-1f4838)](https://modelcontextprotocol.io)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Release](https://img.shields.io/github/v/release/vicmaster/framesmith)](https://github.com/vicmaster/framesmith/releases) [![MCP](https://img.shields.io/badge/MCP-compatible-1f4838)](https://modelcontextprotocol.io) [![CI](https://github.com/vicmaster/framesmith/actions/workflows/ci.yml/badge.svg)](https://github.com/vicmaster/framesmith/actions/workflows/ci.yml)

An open-source MCP server that turns your AI coding agent into a capable UI designer. It gives the agent a visual canvas, a library of vetted design patterns, and a quality bar it must clear before showing you anything — so you review a real, non-slop design in the browser and agree on it *before* any framework code is written.

Expand Down
6 changes: 3 additions & 3 deletions VISION.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -543,10 +543,10 @@ _Full spec-driven breakdown in [`docs/specs/PHASE-29-SPEC.md`](docs/specs/PHASE-

### Continuous integration (infrastructure)

Nothing runs on a pull request today: no workflow, no branch protection, no checks. That held because a single disciplined maintainer ran things by hand, but it has already failed silently — the repository has 102 test files and no `test` script to run them as a suite, and two tests were found failing at the v2.0.0 tag itself. The missing runner is the root cause; a pipeline that is red on its first run only teaches you to ignore the red X.
Nothing runs on a pull request today: no workflow, no branch protection, no checks. That held because a single disciplined maintainer ran things by hand, but it has already failed silently — the repository has 102 test files and no `test` script to run them as a suite, and two tests were found failing at the v2.0.0 tag itself. The missing runner is the root cause; a pipeline that is red on its first run only teaches you to ignore the red X. Running the suite together for the first time turned up three failing tests, two of them red at the v2.0.0 tag itself; all three are fixed.

- [] Slice A — a tiered runner (`test:fast` / `test:full`), the interactive viewer test excluded by name, `test-cli` moved to the Chrome tier, the stale `data-table` assertions rewritten against the scaffold as it exists now, the tabular-figures advisory regression investigated and settled in whichever direction the evidence supports, and the network/API-key tests confirmed to skip rather than fail when offline or keyless
- [] Slice B — a GitHub Actions workflow on pull requests and pushes to master: a fast job (build as type check, plus the Chrome-free tier) and a separate Chrome job, no secrets required, with a pinned Node version and a matching `engines` field
- [x] Slice A — a tiered runner (`test:fast` / `test:full`), the interactive viewer test excluded by name, `test-cli` moved to the Chrome tier, the stale `data-table` assertions rewritten against the scaffold as it exists now, the tabular-figures advisory regression investigated and settled in whichever direction the evidence supports, and the network/API-key tests confirmed to skip rather than fail when offline or keyless
- [x] Slice B — a GitHub Actions workflow on pull requests and pushes to master: a fast job (build as type check, plus the Chrome-free tier) and a separate Chrome job, no secrets required, with a pinned Node version and a matching `engines` field

_Full breakdown in [`docs/specs/CI-SPEC.md`](docs/specs/CI-SPEC.md). Deliberately out of scope: branch protection (the checks should earn trust before they can block a merge), a test framework (the plain-script convention works; rewriting 102 files is unrelated to what is broken), release automation, and coverage measurement._

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,9 @@
"dist",
"docs/GUIDELINES.md"
],
"engines": {
"node": ">=22"
},
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build",
Expand Down
28 changes: 16 additions & 12 deletions src/structures.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -885,11 +885,12 @@ const auth: Structure = {
id: 'au-fields', type: 'frame', width: '100%', layout: 'vertical', gap: SPACE.md,
children: [field('au-email', 'Email'), field('au-password', 'Password')],
},
{
id: 'au-submit', type: 'frame', name: 'Submit', width: '100%', layout: 'horizontal',
alignItems: 'center', justifyContent: 'center', padding: [SPACE.xs, SPACE.lg], cornerRadius: RADIUS.sm, fill: COLOR.accent,
children: [{ id: 'au-submit-label', type: 'text', content: 'Continue', fontSize: TYPE.body, fontWeight: 600, color: COLOR.bgPrimary }],
},
// Hand-rolled until CI caught it: this was the one button in the
// library not built from `button()`, so it missed the minWidth /
// overflow / ellipsis hardening and clipped under a long label.
// It only failed on Linux — macOS font metrics left just enough
// room — which is why the Phase 29 scaffold sweep passed it.
{ ...button('au-submit', 'Continue', COLOR.accent, COLOR.bgPrimary), name: 'Submit', width: '100%' },
{ id: 'au-alt', type: 'text', content: 'Secondary link', fontSize: TYPE.textSm, fontWeight: 500, color: COLOR.accent, textAlign: 'center' },
],
},
Expand DownExpand Up@@ -1146,23 +1147,26 @@ function tableRow(id: string): SceneNode {
],
},
{
id: `${id}-role`, type: 'frame', width: '18%', layout: 'horizontal',
id: `${id}-role`, type: 'frame', width: '18%', layout: 'horizontal', minWidth: 0,
children: [{
id: `${id}-role-chip`, type: 'frame', layout: 'horizontal', alignItems: 'center', padding: [SPACE.xs2, SPACE.xs],
cornerRadius: 999, fill: COLOR.bgElevated,
children: [{ id: `${id}-role-text`, type: 'text', content: 'Role', fontSize: TYPE.caption, color: COLOR.textSecondary }],
cornerRadius: 999, fill: COLOR.bgElevated, minWidth: 0, overflow: 'hidden',
children: [{ id: `${id}-role-text`, type: 'text', content: 'Role', fontSize: TYPE.caption, color: COLOR.textSecondary, textOverflow: 'ellipsis' }],
}],
},
{
id: `${id}-status`, type: 'frame', width: '18%', layout: 'horizontal', alignItems: 'center', gap: SPACE.xs2,
id: `${id}-status`, type: 'frame', width: '18%', layout: 'horizontal', alignItems: 'center', gap: SPACE.xs2, minWidth: 0,
children: [
{ id: `${id}-status-dot`, type: 'ellipse', width: 8, height: 8, fill: '$success' },
{ id: `${id}-status-text`, type: 'text', content: 'Status', fontSize: TYPE.caption, color: COLOR.textSecondary },
{ id: `${id}-status-text`, type: 'text', content: 'Status', fontSize: TYPE.caption, color: COLOR.textSecondary, textOverflow: 'ellipsis' },
],
},
{
id: `${id}-amount`, type: 'frame', width: '18%', layout: 'horizontal', justifyContent: 'end',
children: [{ id: `${id}-amount-text`, type: 'text', content: 'Amount — TBD', fontSize: TYPE.textSm, color: COLOR.textPrimary, tabularNums: true }],
// The identity cell above has carried minWidth + ellipsis since Phase 29;
// these three columns never got it, and overflowed on Linux where the
// fonts are wider than the macOS metrics the sweep was run against.
id: `${id}-amount`, type: 'frame', width: '18%', layout: 'horizontal', justifyContent: 'end', minWidth: 0,
children: [{ id: `${id}-amount-text`, type: 'text', content: 'Amount — TBD', fontSize: TYPE.textSm, color: COLOR.textPrimary, tabularNums: true, textOverflow: 'ellipsis' }],
},
{
id: `${id}-actions`, type: 'frame', width: '12%', layout: 'horizontal', justifyContent: 'end',
Expand Down
Loading