Skip to content

Rebuild the interface on Picotron: palette, pixel verification, flat surfaces, a real framebuffer - #1

Merged
kaidesu merged 12 commits into
mainfrom
claude/lumen-aseprite-gui-library-nq5qnc
Sep 2, 2026
Merged

kaidesu merged 12 commits into
mainfrom
claude/lumen-aseprite-gui-library-nq5qnc

Conversation

@kaidesu

@kaidesu kaidesu commented Sep 2, 2026 •

Copy link
Copy Markdown
Member

Rebuilds Studio's interface on Picotron: the palette, a pixel-verification tool that survives real screenshots, the art redrawn from measurements, and the widgets moved onto a magnified low-resolution framebuffer.

The comparator was broken in a way that would have invalidated everything

Three of the five reference screenshots come through a capture pipeline that darkens every channel by up to 9 — #fff1e8 arrives as #f6eee6. A pixel-perfect reproduction scores 48.8% against them, and the failure looks exactly like a broken renderer.

Snapping each colour to its nearest palette entry is the obvious fix and a worse bug, because it fails silently on a few pixels rather than loudly on all of them. Measured against real capture pairs it reads #6f478e as #654688 when it is really a shifted #754e97 — those purples sit 23.3 apart and the shift moves colours up to 15.6, so the nearest entry is the wrong entry. Both are window chrome, so the corruption lands precisely where the matching matters.

So the transform is applied rather than guessed at. Seventeen colours appear in both an accurate and a shifted capture, giving a measured true → captured channel map that is a function and monotonic, so it inverts. The tool derives a shifted twin of the palette, detects which profile fits each image, and matches within it. All seventeen pairs recover correctly; a shifted reference against its canonical twin scores 100%.

The palette is five ramps of five — and is not complete

Inverting that map also recovered most of the palette. A palette-file thumbnail in one reference turns out to be a 5×5 grid that is not an arbitrary set of colours but five ramps, dark to light, with PICO-8's sixteen scattered through them.

The ramps matter more than the list: a face is a step, hovered is the step above, pressed the step below. That makes "one step lighter" a rule the whole interface shares instead of forty hand-picked colours that only nearly agree.

An earlier revision of this PR claimed the set was complete at 32. That came from 25 ramp entries plus 7 others, and 32 is a size Picotron documents, so the arithmetic looked like independent confirmation. It was a coincidence of the screenshots to hand: a later reference — Picotron's icon browser — uses an orange and a magenta the set cannot explain at any distance. Picotron's system palette is 64, so more will turn up.

What confirms an entry is the fit score, not a count. Adding those two dropped the icon browser from 0.41 to 0.04 and the file browser from 0.19 to 0.01, and a poor score is how the next missing colour will announce itself.

There is now a check that can see a pixel

tools/verify.sh renders through Xvfb at Picotron's 480×270 framebuffer, reads the frame back, and compares eight chrome regions against crops of Picotron's own screenshot. All eight match exactly, in CI as well as locally. The gate is real, not decorative: changing the corner chamfer from 2px to 3px fails five of the eight and exits non-zero — tested by doing it.

It earned itself immediately. The first render came back at 88.8% on the top-left corner because the chamfer was applied to all four corners of the title bar, leaving a one-pixel notch where Picotron's is square. No test would ever have found that.

Controls are not outlined, which was most of the problem

Measured off two Picotron controls: a button is a flat #c2c3c7 fill on the #fff1e8 body with one pixel off each corner and no border of any kind, and a tab strip is flat #83769c. Only windows are outlined.

This kit outlined everything — every button, cell, field, tab and track carried a 1px black border. That is the largest single difference between an interface that looks like Picotron and one that merely uses its palette: forty black lines around things Picotron leaves as flat tone against flat tone, which reads as heavy and busy without any one control being identifiably wrong.

The corollary is that a control is read entirely by its tone against its ground. A toolbar item and a button are therefore different things rather than one thing on two grounds — Picotron's toolbars carry bare icons with no button shape, and filled buttons only appear on the light body. Two things keep borders, both measured: a checkbox (9×9, 1px border — it earns one by being small) and a scrollbar thumb (solid #83769c on a light track).

Geometry and art, measured rather than assumed

  • The corner is a chamfer, not a curve — a straight 45° cut, [2, 1] on every window in every reference, 1px on controls.
  • The title bar is 11 rows of fill over a 1px rule, identical across three windows in three references; only the colours move.
  • The outline colour is a theme role, not a derived rule: one window uses #ff77a8 against a #ffacc5 title, exactly one ramp step apart; two others use black, one against that same fill.
  • Picotron uses two icon languages — monochrome 7×7 tintable silhouettes for controls, full-colour 16×16 pictures with a navy outline for files. The old sheet used 16×16 cells against a 12px row, so every icon was taller than the row containing it.
  • The file icons are Picotron's own art, lifted pixel for pixel from its icon browser, which shows the set at 1:1. Every colour resolves to a palette entry at distance zero.
  • The pointer is a hollow outline, not a filled arrow with a border. Reproduced pixel for pixel at 100%.

Icon art lives in tools/make-icons.py as ASCII with the PNGs as build output, so changing an icon is a readable diff rather than an unreviewable blob.

The framebuffer

Studio draws into a magnified low-resolution framebuffer rather than at the window's own resolution. Picotron's 12px rows and 7×7 icons only mean anything if a drawn pixel covers several screen pixels.

The magnification is chosen rather than fixed: logicalSize() picks the integer scale putting the logical height nearest 270 and divides the window by it, so 1920×1080 gives exactly 480×270 at 4× while 1440×900 gives 480×300 at 3×. A wider monitor buys workspace instead of margins, which matters because Studio is an editor inside a window and not the whole operating system. Whole numbers only — a fractional scale resamples every drawn pixel to a different width.

What rendering found that nothing else could

lumen . --shot run:app.preferences renders one frame of any screen, including those only reachable by clicking, and quits. Every one of these was invisible to the test suite and the linter:

  • A 32×32 sprite opened at a fixed 12× zoom — 384px of artwork in a 170px region, so the canvas showed a horizontal slice through the middle of the sprite. It read as a stretched canvas, not a zoom fault.
  • The transparency checker was a 48px chessboard: 16px is Aseprite's default and correct when 16px means 16 screen pixels.
  • Scrollbar.thumbRect declared a local named span, which shadows this.span() for the whole call — painting a scrollbar raised "is a number, which cannot be called". Shipped and never run. The linter checks for it now, verified against the original bug.
  • The modal scrim was solid black, so opening a dialog blanked the workspace instead of putting it out of reach.
  • Removing control outlines made every button vanish, because button.face matched the panel it sat on.
  • Palette swatches had a 1px fringe of their own colour: outline() draws with line primitives and pixel snapping while fill() draws a rectangle, and the two disagree by a pixel.

Checks

119 assertions, a clean linter, and all eight pixel tiles matching — CI runs the pixel job alongside test, lint and the parse sweep.

Known gaps

  • The tool icons have no reference behind them. Pencil, eraser, bucket, picker, line and rectangle are designed in Picotron's idiom rather than measured from it; no screenshot of its gfx editor has been available. Everything else in the interface is measured.
  • The font is a compromise, not a match. Picotron's cap is 5px; silver.ttf is mangled at 8, 9 and 10px — strokes drop out and descenders vanish — so 12px is the first clean size and its cap is 6px. A real 5px pixel face would close it; ui.font takes one.
  • docs/tutorial.html documents the interface this PR replaced and says so at the top. Its structural half and the Ghost papercut reference are unaffected; the painting and theming chapters are not. Rewriting it is outstanding.

🤖 Generated with Claude Code

https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM

… screenshots

The pixel-matching tool had a flaw that would have invalidated every
measurement taken with it. Three of the five reference screenshots come
through a capture pipeline that darkens every channel by up to 9 - #fff1e8
arrives as #f6eee6 - so a pixel-perfect reproduction scores 48.8% against
them, and the failure looks exactly like a broken renderer.

Snapping each colour to its nearest palette entry is the obvious fix and a
worse bug: it fails silently on a few pixels rather than loudly on all of
them. Measured against real capture pairs it reads #6f478e as #654688 when
it is really a shifted #754e97 - those purples sit 23.3 apart and the shift
moves colours up to 15.6, so the nearest entry is the wrong entry. Both are
window chrome, so the corruption lands where the matching matters.

So the transform is applied rather than guessed at. Seventeen colours appear
in both an accurate and a shifted capture, giving a measured true -> captured
channel map that is a function and monotonic, so it inverts. The tool now
derives a shifted twin of the palette, detects which profile fits each image,
and matches within it. All seventeen pairs recover correctly, and a shifted
reference against its canonical twin scores 100%.

Inverting that map also recovered the rest of the palette. A palette-file
thumbnail in one reference turns out to be a 5x5 grid that is not an
arbitrary set of colours but five ramps of five, dark to light, with PICO-8's
sixteen scattered through them. Twenty-five ramp entries plus seven confirmed
colours whose ramp position was never shown is thirty-two, the size Picotron
documents - so the set is complete rather than as much as could be found.
Adding the sixteen recovered entries dropped one reference's palette fit from
3.10 to 0.19, which is the independent check that they are right.

The ramps matter more than the list. A ramp is how a surface gets its states:
a face is a step, hovered is the step above, pressed is the step below. That
makes "one step lighter" a rule the whole interface shares instead of forty
hand-picked colours that only nearly agree.

The data and the clamping live in support/ with no `lumen:` import so the
tests can reach them, leaving palette.gs holding only the part that genuinely
needs the engine. That split immediately earned itself: it caught `steps.length`
used as a property where Ghost wants `length()`, which would have crashed on
the first repaint.

97 assertions pass; the linter is clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
…uild

There is now a check that can see a pixel. tools/verify.sh renders verify.gs
through Xvfb at Picotron's 480x270 framebuffer, reads the frame back, and
compares eight chrome regions against crops of Picotron's own screenshot.
All eight match exactly.

The gate is real rather than decorative: changing the corner chamfer from 2px
to 3px fails five of the eight tiles and exits non-zero. That was tested by
doing it.

It earned itself immediately. The first render came back at 88.8% on the
top-left corner, and the reason was a bug no test would ever have found: the
chamfer was applied to all four corners of the title bar, so its last row was
inset by one where Picotron's is square, leaving a one-pixel notch against the
rule below. `chamfered()` now takes [topLeft, topRight, bottomRight,
bottomLeft]. Per-corner control is not a refinement here, it is the difference
between right and wrong.

Two findings behind the geometry, both measured rather than assumed:

Picotron's corner is a chamfer, not a curve - a straight 45-degree cut giving
back exactly one pixel a row, profile [2, 1] on every window in every
reference. It gets its own helper rather than a flag on cornerInsets(),
because the two shapes disagree from radius 3 upward and neither should be
bent into approximating the other.

The surface is a flat fill inside a one-pixel outline, with the inner shape
deflated by one and cut one less deeply - which is what keeps the outline an
even thickness along the diagonal instead of pooling at the corner. There is
no bevel anywhere in any reference, and adding one is the single change that
would most obviously mark this as an imitation.

The outline colour is a theme role, not a derived rule. One window uses
#ff77a8 against a #ffacc5 title, exactly one ramp step apart, which looked
like a law worth having; another uses black against the same title fill. Two
windows, same fill, different edges - so the ramp relationship is offered to
themes rather than imposed on them.

103 assertions pass, the linter is clean, and CI now runs the pixel job too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
Picotron uses two icon languages, and reading them as one is why the old sheet
could not have worked. Control icons - tools, toolbar buttons, arrows - are
monochrome 7x7 silhouettes in a single tint, drawn on the toolbar's own ground
with no outline. File icons - folder, document, cartridge - are full-colour
15x16 pictures with a 1px #1d2b53 outline and two fill tones. One is tintable
and one is not, so they are now two sheets.

The size mattered as much as the style. Picotron's control icons are 7x7 in an
8x8 cell against a 12px row; the old sheet used 16x16 cells, so every icon was
taller than the row containing it. No amount of redrawing would have fixed
that, and it is most of why the interface read as messy.

The pointer is reproduced exactly rather than approximated: Picotron's is a
hollow outline, not a filled arrow with a border, so every interior pixel is
whatever is behind it. Measured off the reference and confirmed at 100% by
pixelmatch. The grid toggle is likewise Picotron's own, pixel for pixel.

The art now lives in tools/make-icons.py as ASCII and the PNGs are build
output, so changing an icon is a readable diff instead of a binary blob nobody
can review.

103 assertions pass, the linter is clean, and all eight pixel tiles still
match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
The full-resolution capture of the purple desktop measures the same as the two
already recorded: eleven rows of title fill, then a one-pixel rule, in every
window in every reference. Only the colours move.

That is the finding worth writing down. One window draws a #ff77a8 edge against
a #ffacc5 title, exactly one step apart on the red ramp, which looked like a
law worth deriving; two others draw black, one against that same fill. Same
fill, different edge - so the ramp relationship is offered to themes rather
than imposed on them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
…a real framebuffer

Studio now draws into a magnified low-resolution framebuffer rather than at the
window's own resolution, which is the change everything else depended on.
Picotron's 12px rows and 7x7 icons only mean anything if a drawn pixel is
several screen pixels wide; at 1:1 on a modern display they are not chunky,
they are microscopic.

The magnification is chosen rather than fixed. logicalSize() picks the integer
scale putting the logical height nearest 270 and divides the window by it, so
1920x1080 gives exactly Picotron's 480x270 at 4x while 1440x900 gives 480x300
at 3x. A wider monitor buys workspace instead of margins, which matters because
Studio is an editor inside a window and not the whole operating system: at a
hard 480x270 a sprite plus a palette, timeline and two toolbars leaves almost
no canvas. Whole numbers only - a fractional scale resamples every drawn pixel
to a different width, which is the one rule that cannot bend.

The theme sources every colour from palette.step() or palette.named(), so none
can drift off the thirty-two. A hex two off a real entry looks fine alone and
wrong beside everything else, which is exactly the kind of mistake that
accumulates into an interface that reads as messy without any one thing being
identifiably broken.

raised/sunk/panel/well are reimplemented on the flat surface, which converted
all thirty-two call sites without touching a widget. Losing the bevel sounds
like losing vocabulary and is not: with a ramp behind it, pressed is one step
darker and hovered one step lighter, and that reads better at 12px than a
one-pixel highlight ever did. The groove went the same way - a dark rule with a
light one under it did more for the "real tool" feel than any other detail in a
bevelled interface, and at a 12px row it is a thick black bar.

Rendering the gallery is what found the rest. The heading colour was the light
purple Picotron paints title bars with, which vanishes as text on toolbar grey
- invisible in a swatch, obvious the moment twenty controls sit together.

It also surfaced a bug that had been shipped and never run: Scrollbar.thumbRect
assigned a local named `span`, which shadows this.span() for the whole call, so
painting a scrollbar raised "is a number, which cannot be called". Same family
as a method name shadowing an import - Ghost's lookup reaches the enclosing
scope before the class. The linter now checks for it, and was confirmed against
the original bug.

113 assertions pass, the linter is clean, and all eight pixel tiles still match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
main.gs now draws into the magnified framebuffer with the Picotron theme, and
`lumen . --shot` renders one frame, saves it and quits - the only way to look
at the interface on a machine with no display, and the only way a build can
check that the thing still draws at all.

Doing that immediately showed two faults that no test could have.

The canvas was not square. A 32x32 sprite opened at a fixed 12x zoom, which was
a reasonable default when the interface drew at window resolution and is badly
wrong at 480x270: 384px of artwork in a region 170px tall, so what the canvas
showed was a horizontal slice through the middle of the sprite. It read as a
stretched canvas rather than a zoom fault, which is why it took a screenshot to
find. The viewport now fits the document the first time it is given a size, and
never again - once someone has zoomed, a resize must not throw that away.

The transparency checker was a 16px square, Aseprite's default and correct when
16px meant 16 screen pixels. Here every framebuffer pixel covers three or four
of them, so it landed as a 48px chessboard that read as the subject of the
canvas rather than its background. It is a theme metric now, and Picotron's is
eight.

Both are the same underlying mistake: a constant tuned for one resolution
surviving into another. The linter cannot see those and neither can the test
suite; only rendering the app can.

120 assertions pass, the linter is clean, and all eight pixel tiles still match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
…tte claim

The icon browser screenshot shows Picotron's file-type set at 1:1 on a 480x270
screen - a 16x16 grid at pitch 40 - so the glyph sheet is now its art rather
than my transcription of it. Every colour resolves to a palette entry at
distance nought, which is the check that the copy is exact and not merely
close. Transparency is flood-filled from each cell's border rather than matched
on colour, because the paper inside a document is #fff1e8, the same as the
browser's own page; matching on colour hollows out every icon, which it duly
did on the first attempt.

The same screenshot showed that a claim in the last commit was wrong. This
palette is not complete at thirty-two. That number came from twenty-five ramp
entries plus seven others, and thirty-two is a size Picotron documents, so the
arithmetic looked like independent confirmation - it was a coincidence of the
screenshots to hand. The browser uses an orange and a magenta that the set
cannot explain at any distance, and Picotron's system palette is sixty-four, so
more will turn up.

Both are recorded now, and the fit scores are what confirm them rather than any
count: adding the two dropped the icon browser from 0.41 to 0.04 and the file
browser from 0.19 to 0.01. The test that asserted a total has been replaced -
it encoded the wrong claim - and detect_profile() reporting a poor fit is how
the next missing entry will announce itself.

119 assertions pass, the linter is clean, all eight pixel tiles still match, and
the app still renders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
The Window widget was the last thing still painting through the old bevelled
path. It now draws the measured Picotron window: a flat body inside a
one-pixel outline cut [2, 1], a title bar of eleven rows, a one-pixel rule
under it, and the title centred - which is what every window in every
reference does. The bar is cut on its top corners only; cutting all four
leaves a notch of body showing at each end, the same fault that took the
chrome from 100% to 88.8% the first time it was rendered.

`lumen . --shot run:app.preferences` runs a command before the frame is taken,
so a screen reachable only by clicking can be looked at on a machine with no
display. Those are exactly the screens least likely to have been looked at,
and rendering this one found four faults at once:

The scrim was solid black, so opening a dialog blanked the workspace entirely
rather than putting it out of reach. It is the one colour in this theme that
cannot come from the palette, because a scrim is a compositing operation and
not an entry.

The dialog was 320x200, which was reasonable at window resolution and is two
thirds of a 480x270 framebuffer. That is not a dialog, it is a takeover. At
210x126 it fits its content with room to spare, which took two goes - the
first was short enough to clip its last row.

Picotron was missing from the theme picker, so the running theme was the one
theme that could not be selected and every radio read as off.

119 assertions pass, the linter is clean, and all eight pixel tiles still match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
Three faults that only rendering could show, all in the same family: a widget
that reads fine on its own and wrongly in place.

The slider drew its label in one colour across a bar that is filled on one side
and empty on the other, so the text went muddy over the fill. It is now drawn
twice, clipped to either side of the fill edge - light where it crosses the
bar, dark where it crosses the track. Neither colour alone can do both, and
picking a fill pale enough for dark ink would leave the bar barely visible
against the track it sits in. The seam falls mid-glyph and reads as intended.

The current-colour chips were anchored to the bottom of the colour bar, which
is docked down the full height of the window - so the two colours being edited
sat several hundred pixels from the swatches they are chosen from, with a rule
floating above them marking a boundary between nothing and nothing. They now
sit directly under the palette.

Every palette swatch had a fringe of its own colour leaking past its border on
the right and bottom. outline() draws with line primitives and pixel snapping
while fill() draws a rectangle, and the two disagree by a pixel. Swatches and
chips are surfaces now, which are spans only and cannot drift.

Fixing that made a fourth fault obvious: the selected swatch was marked with a
bevel plus an accent line drawn in the gap above it, which reads as a smudge
rather than a selection. Selection is the colour of the swatch's own border
now - unambiguous on a black swatch and a white one alike, where a tick would
need to be two different colours and is unreadable at 8px regardless.

119 assertions pass, the linter is clean, and all eight pixel tiles still
match - in CI too: the pixel job built Lumen, rendered through Xvfb and
compared every tile at 100%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
Measured off two Picotron controls: a button is a flat #c2c3c7 fill on the
#fff1e8 body with one pixel off each corner and NO BORDER OF ANY KIND, and a
tab strip is a flat #83769c with white text on it. Only windows are outlined.

This kit outlined everything - every button, cell, field, tab and track carried
a one-pixel black border. That is the largest single difference between an
interface that looks like Picotron and one that merely uses its palette: forty
black lines around things Picotron leaves as flat tone against flat tone, which
reads as heavy and busy without any one control being identifiably wrong. It is
why it survived this long.

Controls also take a smaller corner than windows do - one pixel against two -
so the two cuts are separate metrics now rather than one shared number.

Rendering the change found the other half of the rule. With no border to fall
back on, a control is read entirely by its tone against its ground, and
button.face matched the panel it sat on, so every button simply vanished.
Picotron's button is the darker of its pair because it sits on the white body;
ours mostly sit on grey toolbars, so ours is the lighter one. The rule is that
there is a step, not which direction it goes in.

Two things keep their borders, and both are measured rather than kept out of
habit. A checkbox is 9x9 with a 1px border: it earns one by being small, where
a 9px flat fill one tone from its ground is a smudge and a 40px button is
plainly a button. And a scrollbar thumb is solid #83769c on a light track,
which is not the button tone - taking that left the thumb the same colour as
its own groove.

119 assertions pass, the linter is clean, and all eight pixel tiles still match:
the window chrome is the one thing this did not change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
The Preferences dialog's - and + buttons disappeared. They sit on the white
window body, and button.face had been moved to the light tone so that buttons
would read on the grey toolbars - so on a light ground there was no step left
and, with no border to fall back on, nothing to see.

The fix is not a second button colour. It is that Picotron does not put filled
buttons on toolbars at all: its file browser draws view and navigation controls
as bare #83769c icons directly on the #c2c3c7 bar with no button shape behind
them, and a filled button - #c2c3c7 on the #fff1e8 body - only ever appears on
a light ground. A toolbar item and a button are different things, not one thing
on two grounds.

So Button gains bare(), which draws no fill until hovered, pressed or selected,
and the toolbar uses it. button.face goes back to the darker neutral, which is
the tone Picotron actually fills a button with. One fill now serves the whole
interface because the two cases were separated rather than averaged.

Also records the no-outline rule in docs/picotron.md, which was the spec the
work is measured against and was missing the most important thing in it.

119 assertions pass, the linter is clean, and all eight pixel tiles still match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
docs/tutorial.html builds the pre-Picotron design: bevelled surfaces with a
highlight-and-shadow pair, circular corner profiles, a 16px icon grid, free hex
per theme, and rendering at the window's own resolution. The code now does none
of those - flat fills into a magnified 480x270 framebuffer, corners cut rather
than rounded, windows outlined but controls not, every colour indexed to a
fixed palette.

Eighty passages in it are now wrong. Docs that are quietly wrong are worse than
docs that are visibly old, because someone following along would build
something the repository no longer contains and conclude they had made a
mistake. The notice says which half is still true: the structural reasoning -
the widget tree, the dock, capture-based input, commands, the keymap - and the
Ghost papercut reference are unaffected, because none of that changed. The
chapters on painting and theming are the ones that did.

Rewriting it against the current design, and reordering it so the widget
playground comes first and Studio is the final chapter, is outstanding work
rather than work quietly dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QF46RxyogNPLNX7DajyKM
@kaidesu kaidesu changed the title Rebuild on Picotron: recover the palette, prove pixel matching, redraw the art Rebuild the interface on Picotron: palette, pixel verification, flat surfaces, a real framebuffer Sep 2, 2026
@kaidesu
kaidesu marked this pull request as ready for review September 2, 2026 15:09
@kaidesu
kaidesu merged commit 31ea5f3 into main Sep 2, 2026
2 checks passed
@kaidesu
kaidesu deleted the claude/lumen-aseprite-gui-library-nq5qnc branch September 2, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants