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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ out
dist
*.log
.e2e-profile
.e2e-shots/
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ so an update never silently changes what an existing user sees; the bridge to ma
userData), never shared. `settings/options.ts` lists every terminal option by id; a unit test holds
the list and the sections together, and each app's e2e (`options`) asserts its page shows that
list and no terminal-looking row of its own. A row outside the list is a fork.
- **A PAGE THAT WORKS IS NOT A PAGE THAT LOOKS RIGHT** (#20, 2026-09-19). Moving the settings into
`core/` dropped every Tailwind class used only there (`core/` is outside the scanned root; the fix
is the `@source` line at the top of `index.css`, do not remove it). All 14 e2e scenarios passed over
a ruined page, because they asserted that rows EXIST. The `options` scenario now MEASURES the
layout (card width, wall rows, row padding) and writes `.e2e-shots/settings-*.png`. After any change
that moves UI between `src/` and `core/`, LOOK at those screenshots before calling it done.
- **A terminal change goes in `core/`**, and is a change to Prism too: say so in the PR, and ask the
owner when it would conflict with how Prism works. App-shell changes (tabs, start screen, window)
stay in `src/`.
Expand Down
6 changes: 5 additions & 1 deletion core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ ask me."* And: *"why can't this repo be the core?"* It can, and this is it.

## How each app consumes it

**Prism Terminal** (this repo): directly, through the app-side alias `@core`.
**Prism Terminal** (this repo): directly, through the app-side alias `@core`,
plus ONE line in `src/renderer/src/index.css`: `@source '../../../core';`. Tailwind
scans from the renderer root down and `core/` is outside it, so without the line
a utility used only in here is never generated, silently (#20: it took the whole
Settings page apart, with every functional test green).
The app's tests, lint and the 14 e2e scenarios are the core's gate.

**Prism**: as a dev dependency pinned to a tag of the `core-dist` branch, which
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "prism-terminal",
"productName": "Prism Terminal",
"version": "0.1.0",
"version": "0.1.1",
"description": "A tabbed Windows terminal for AI CLIs.",
"main": "./out/main/index.js",
"author": "Max",
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/src/index.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
@import 'tailwindcss';
/* THE CORE IS OUTSIDE THE FOLDER TAILWIND SCANS (#20). Tailwind looks for class
names from the renderer root down, and the terminal lives in core/ at the
repo root, so a utility used ONLY there is never generated - silently. It
went unnoticed while every class in core/ also appeared in src/, and took the
whole Settings page apart the day the settings moved into the core. Prism
carries the same line for the same reason (see core/README.md). */
@source '../../../core';

:root {
color-scheme: dark;
Expand Down
34 changes: 34 additions & 0 deletions tools/e2e/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,40 @@ const scenarios = {
const extra = [...shown].filter((id) => !wanted.includes(id) && !own.includes(id))
ok(extra.length === 0, `and nothing else claims to be a setting (extra: ${JSON.stringify(extra)})`)
ok((await page.locator('[data-pref="confirm-close"]').count()) === 0, 'the close question is not a setting any more')
// AND IT IS LAID OUT (#20). The rows above all existed and all worked while
// the page was a ruin: core/ sits outside the folder Tailwind scans, so
// every utility used only by the shared sections was never generated, and
// a check that a row EXISTS cannot see that. Measured, so it can: a theme
// card has a card's width and the wall wraps into rows, a row has its
// padding, and the options of a segmented control do not overlap.
const look = await page.evaluate(() => {
const box = (e) => e.getBoundingClientRect()
const cards = [...document.querySelectorAll('[data-term-card]')].map(box)
const row = document.querySelector('[data-pref="term-font"]')
return {
cardWidth: Math.round(cards[0]?.width ?? 0),
cardRows: new Set(cards.map((c) => Math.round(c.top))).size,
rowPad: row ? parseFloat(getComputedStyle(row).paddingTop) : 0
}
})
await page.screenshot({ path: resolve(process.cwd(), '.e2e-shots/settings-appearance.png') }).catch(() => {})
ok(look.cardWidth >= 150, `a theme card is a card, not a sliver (${look.cardWidth}px wide)`)
ok(look.cardRows >= 2, `and the wall wraps into rows (${look.cardRows})`)
ok(look.rowPad >= 8, `a settings row has its padding (${look.rowPad}px)`)
await page.locator('[data-settings-tab="general"]').click()
await sleep(300)
const overlaps = await page.evaluate(() => {
let n = 0
for (const row of document.querySelectorAll('[data-pref]')) {
const b = [...row.querySelectorAll('button')].map((e) => e.getBoundingClientRect()).filter((r) => r.width > 0)
for (let i = 0; i < b.length; i += 1)
for (let j = i + 1; j < b.length; j += 1)
if (b[i].left < b[j].right - 1 && b[j].left < b[i].right - 1 && b[i].top < b[j].bottom - 1 && b[j].top < b[i].bottom - 1) n += 1
}
return n
})
ok(overlaps === 0, `no two controls in a row overlap (${overlaps} do)`)
await page.screenshot({ path: resolve(process.cwd(), '.e2e-shots/settings-general.png') }).catch(() => {})
await app.close().catch(() => {})
},

Expand Down
Loading