Repository navigation
Releases
v0.12.1
check stops Chrome by closing its debugging pipe, so Chrome exits cleanly. On macOS, Chrome ended by a signal left a copy of the whole app (its code-signing clone, about 1,340 files) in the user's temp area on every run; a clean exit removes it. A Chrome that does not exit within 3 s still gets SIGTERM, then SIGKILL.
v0.12.0
A circuit is a ```schematic fence: a netlist that pack places, wires and draws as one SVG.
A page with a Chart is about 500 KB smaller: pack carries ECharts compressed and the page unpacks it as it loads.
A page where the reader must judge every item now has what it needs: each Records row can carry the reader's answer, groups can start open, the block can use the window's width, and the reply lists answers with their notes.
What an author does differently
- Write each part on a line, nets by name, and where it goes:
Q1 c b e BC547 @0,0,R_C vcc c 4.7k b@Q1.c 0,-1 v(this resistor'sbpin one unit above Q1's collector).packroutes the nets on a grid, adds ground symbols, junction dots and labels, and fails with the line and what to change on a part with no place, an unknown pin, parts on top of each other, a net it cannot route, a label with no room, an IC pin written twice, anncpin that touches anything, or a net that joins one pin or touches another (a part's lead counts as part of its pin). Symbols: resistor, capacitor, electrolytic, inductor, diode, zener, LED, switch, battery, voltage and current source, potentiometer, NPN and PNP, N and P MOSFET, op-amp, IC with named pins, terminals, supply rails and ground.kit schematichas the reference. - Parts link to the prose with
data-ref="R_C"; a net's name (vcc,gnd) lights its wires and junctions. Grounds always hang down: a pin on ground that faces up failspackand tells you to turn the part. A label must be nearer its own part than any other. The drawing follows light, dark and a page's tokens, fills the column up to part names at 16 px, shrinks to 10 px text and then scrolls sideways on a phone, and fits the paper in print. - A page with charts needs no change. The examples with charts go from 1.3 to 1.5 MB to 0.8 to 1.0 MB. ECharts is ready about 100 ms after the page loads (headless Chrome); until then the charts
packdrew stay in place and the live charts replace them as before. It needsDecompressionStream(Chrome 103, Firefox 113, Safari 16.4); an older browser keeps the drawn charts, and a chartpackcould not draw stays empty. - Client code that calls
Charts.makeneeds no change. Code that reads theechartsglobal directly (not through a chart'ssetup) runs before ECharts exists: wait forwindow.echartsReady. Recordstakesreply: { options: ["Agree", "Change", "Need context"], note: true }(the choices as a list of labels orvalue: labelpairs, asQuestion'soptions): every record gets those choices and an optional note beside its row, in the tree, table and board. The answers are kept across reloads like any control, and join the reply under the record's label (its leading text columns). No choice starts chosen, so a row the reader skipped shows. An answer stays when the reader changes view or a filter hides its row and brings it back. A block withreplycarrieskeepandreplyscripts, also when a client component of yours renders it later (one whose props are written out with noreplyand no spread brings neither). New label keysrecordsAnswerandrecordsNote.open: trueopens every group and every record's detail at load;open: 1(or another number up to the depth ofgroupBy) opens that many group levels. The hash writes only what the reader changed from there.width: "full"spans the window from gutter to gutter, beside the contents rail from the rail to the gutter, centred on the column and with the breadcrumbs where they were; it has no maximum width. Phones are unchanged.- Records is keyboard-navigable: one Tab stop on the rows, Up, Down, Home and End between rows, Right and Left to open, close and leave a group in the tree, Enter to follow a row's link or fold,
/to search. A block with areplyis always an island, so the keys work there. - Every reply, from
Questions and row answers alike, lists the answered items with their choice and note together, and folds the unanswered into one line,Not answered: .... An untouched default is still marked(default). Adata-notefield inside adata-replyelement is that element's note. - A
Recordsblock with no field that hassum: truedraws no Total row. - Code blocks wrap on phones, keeping their indentation; wide screens still scroll them.
guide.mdandSKILL.mdsay to show every item whole when the reader must judge each one, and to ask the person who asked for the page when the source cannot say what a reader needs.
v0.11.0
A Chart or Plot is in the packed page as SVG whenever pack can draw it exactly: it shows before the scripts run, with scripts off, and in print. One it cannot (below) appears when the script draws it, as before. One set of Controls can drive figures in several sections, and a written Question with a model answer inside is a self-check that stays on the page.
What an author does differently
- Pages keep writing
<Chart option={...} />and<Plot option={...} />, from the page, in anExploreor in a client component.packdraws each figure three times (a desktop column, half of one, a phone) and the page shows the copy nearest the element's width until the kit script draws it at its own size, without animating over it. Hover values, legends, zoom,setupand Explore's frames work as before, on the live chart. New and optional:heightonChart,PlotandExplore, the figure's height as a length (height="clamp(16rem, 34vw, 21rem)"), whichpackreads. Size figures with it, not with CSS on.chart: the gallery pages moved their.chart.mix { block-size: ... }rules into the prop. - Token colours,
color-mix()and the tone under the chart stay CSS in the drawing, so it follows light and dark and a page's token overrides with no flash of other colours. Colours ECharts mixes from two of them, as avisualMapdoes, are drawn as those two mixed. Keep palette tokens opaque:packreads a mix only between tokens the option lists together. A chart whose colourspackcannot write exactly (a colour function, a token mixed with a literal or translucent colour,colorAlpha,colorLightness,colorSaturation,colorHue, a visualMap'sopacity, amarkAreabeside a translucent colour) is not drawn bypack. - A chart's option functions (formatters) now also run in
pack. A chart whose functions throw there, because they read the page, is not drawn bypackand appears when the script draws it, as before. - A page rule that may match a figure's element and set its size, padding, border,
all,box-sizingor an animation (or the root font size) leaves that figure to the script, as does aheightthat is not a length in px, rem, vw, vh,clamp(),min()ormax(). A Plot's legend that wraps into a different number of rows across the widths a copy is shown at is not drawn either. - For a layout that changes with the chart's width, write ECharts'
mediaqueries in the option rather than reading the element's width in a client component: the phone copy follows them, while code that measures the element runs only in the browser. - A packed page grows by the drawings: about 10 to 20 KB per figure.
- One set of controls can drive several figures and readouts in different sections, with no client component: write
<Controls id="usage" controls={[...]} />once (the same range, choice and toggle controls asExplore), then<Explore use="usage" label="..." frame={daily} />in any section after it. Each Explore takesusein place ofcontrols, shows no row of its own, and keeps everything else (a Plot, a Chart, a readout, stats or a caption; framespackcomputes or a"use client"module exports,base,height).framegets all the set's values in the order the controls are written, so list the ones most figures read first and let a frame take fewer parameters. The choices are kept across reloads like any control with an id. AControlsand its Explores belong in one place: the page outside the Views, or oneView, or one record of a filledView(a reader changes the controls of the place they look at).packfails on ausewith noControlsabove it in its place (naming the sets that are), onusetogether withcontrols, on twoControlswith one id, and on a frame that throws for a combination. Each Explore's precomputed frames still fit 100 KB, so add a control for what changes the figures. Each such figure also shows a quiet line of the chosen values ("7 days · Mac · Breakdown") that links back to the controls, kept in print, and left out on screen when the figure is written right under its ownControls; a toggle-only set with every toggle off says so ("Cached off", new label keyoff).kit Exploredocuments it (kit Controlsprints the same). - A written
Questionwith a model answer inside is a self-check: what the reader writes is kept on the page, stays out of the reply and brings no Reply bar. One with nothing inside still joins the reply. A page that keeps Question answers without a Reply bar gets a Clear answers button at its end once one is saved, which asks before it clears the answers and leaves other controls as the reader set them (new label keysclearAnswers,clearAnswersPrompt). On a page with a Reply bar, a self-check's answer now brings the bar's Clear too.
v0.10.0
One collection of records, shown as a grouped tree, a sortable table or a board, is one component: Records. It replaces Tree. Mermaid diagrams stay readable on phones and in print.
What an author does differently
- Write
<Records {...plan} />with a config object (kit Records): flatdata,idField,fieldswith a kind each,columns, andgroupByfor the tree's levels.views,search,facetsandsortadd the controls; the view, sort, search, filters and open folds go in the URL hash, so<a href="#plan?f.owner=JV">opens a filtered list.linknames a filledVieweach row opens, with previous/next through the rows shown;detailnames a text field each row opens to in place, in the tree, the table and the board alike. - A field with
sum: trueadds subtotals and a total. Records without a value never count as zero: the sum shows what is known plus a question mark,1,372 h + ?(or?when no record has a value), since a missing value may be negative, and screen readers hear how many records it leaves out. - A block inside a
Viewkeeps its state after the view's route:#team?crew?view=table. A view's route ends at its first?, and what follows belongs to what the view holds; client code reads and writes that part withViews.local(el)andViews.setLocal(el, text), which keep the view's trail. Previous and next walk views by their route, so links that carry state (#v/a?panel?x=1) still get them, and follow each link with its state.View'sparamtakes the record id as it is; link to an id with?,#or%in it asencodeURIComponentwrites it (#s/a%3Fb). packchecks the config and fails with the path of each problem (fields.hours.kind) and what to write instead. It also fails on alinkthat names noView, and on a blockidthat is also a View's name, in the page and in each record a filledViewdraws.Treeis gone. Flatten its nesteditemsinto records with the group's name as a field, list those fields ingroupBy, and move a row'stextandrefinto fields named bydetailandrefField. Hand-written.treemarkup still works. Uneven depths and JSX cells have no Records form; write the<details>yourself for those.- New label keys for the block's controls and texts (
kit labelslists them, all starting withrecords).
Mermaid
- Mermaid diagrams stay readable on phones. A flowchart or state diagram too wide for a phone gets a second layout across its direction (left to right becomes top-down), shown below 40rem, so left to right is fine to write. Other diagrams shrink until their smallest text is 10 px, then scroll sideways. Printed, each diagram fits the paper whole, top-down when the left-to-right layout would be too small to read.
- A diagram's width limits are the custom properties
--mermaid-maxand--mermaid-minon itssvg, so one page rule can change them. - A circle node with a short label is about the size of a box node, not a large disc.
- Flowcharts turn back only the edges of a real cycle. An input written after the node it feeds now sits before that node, instead of being looped around it.
check's warning about tiny SVG text names the fix: drop the page rule that squeezes a diagram, or for a hand-built SVG, redraw it about 360 wide or hold it at themin-widthit gives inside a.scrollblock.
check
checkwaits after each load and--dostep until the page has settled instead of a fixed time: fonts loaded, every visible chart drawn, and the timers of up to 1 s the page set have run. A slow island chart is in the shots, and an error a page throws shortly after a click is reported. What is still missing after 10 s is a problem:still waiting after 10 s for charts to be drawn: div#sales.chart.- When Chrome fails to start or stops answering,
checkprints one line naming where it was (the screen, and the--dostep if one was running) and exits 1 with no report, for exampleerror: the load event of file:///…/page.html did not come in 10 s (at dark narrow, view #fix/none). It used to wait forever. - A
checkthat stops with an error, or is ended by Ctrl-C or SIGTERM, leaves--outas it was: the PNGs of a run move there only once it has finished. --tagtakes a name without a path separator;--tag ../xis a usage error, since it would write PNGs outside--out.--timeout <seconds>(default 600) limits the whole run, Chrome's start-up included:error: check passed its 600 s limit at light desktop, view #plan/a, after 324 page(s): raise --timeout for a page with many views or --do steps.- Chrome exits with
checkhowevercheckends, and its profile and temp files are deleted (also on Ctrl-C or SIGTERM, sent once or twice). Before, every run left a profile of up to 150 MB in the temp directory, and with Google Chrome acom.google.Chrome.chrome_chrome_url_fetcher_.*directory, and acheckkilled by a timeout left a Chrome running that used a CPU core. - Chrome no longer downloads components or models while it checks a page.
- On Linux, a
TMPDIRtoo deep for the socket Chrome keeps there (about 50 characters) stopscheckwitherror: Chrome cannot start in a temp directory this deep (…): … set TMPDIR to a shorter one, such as /tmp, instead of Chrome's stack trace.
v0.9.0
Pages are MDX. An agent writes page.mdx: Markdown prose, the kit's components, and the .ts or .json data and .css it imports. pack renders it once to static HTML with Preact and ships code only for client components. Packed v0.8 files still open offline, and check still takes them.
What an author does differently
- Write
page.mdxinstead ofpage.html;packrefuses any other input. There are nokit:references to write:packadds the theme and the kit scripts the page uses. - Prose, lists, tables and formulas are Markdown. A
#and the paragraphs under it are the masthead; each##starts a section; a Markdown table is atable.compare. - Figures and interactions are components imported from
"kit". Behaviour no component covers goes in your own.tsxcomponent whose first line is"use client". - Page settings move into frontmatter:
langinstead of<html lang>,labelsinstead ofvar kitLabels,schemeinstead of<html data-scheme>. - Controls with ids are kept across reloads without
reply.js. Ids repeated on one page failpack. - Images and fonts that code shows are imported (
import icon from "./icon.svg"gives adata:URLpackembeds). A file path written in client code, its props or a filled view's records failspack, since the browser would load it later. example.htmlis nowexample/(page.mdxand the model it imports).
Before (v0.8.2), a step-through figure:
<link rel="stylesheet" href="kit:theme.css">
<figure class="fig" data-stepper data-start="1">
<svg viewBox="0 0 90 20"><rect data-step="1" width="20" height="20" /><rect data-step="2" x="35" width="20" height="20" /></svg>
<ol data-captions><li>The queue is empty</li><li>A burst arrives</li></ol>
</figure>
<script src="kit:steps.js"></script>After (v0.9.0):
import { Stepper } from "kit";
<Stepper start={1} captions={["The queue is empty", "A burst arrives"]}>
<svg viewBox="0 0 90 20"><rect data-step="1" width="20" height="20" /><rect data-step="2" x="35" width="20" height="20" /></svg>
</Stepper>New public names
Components, imported from "kit" (kit <Name> documents each):
Chart:option,label,class,setup. Formatters and labels may be JSON ({ digits: 1, unit: "min" },{ percent: true },{ currency: "EUR", compact: true }, or one text per item).Plot:option(series,categories,horizontal,stack,x,y,legend),label,class. Axisformatand serieslabeltake the same JSON formats; numbers follow the page'slang.Explore:controls(each{ id, label }withrange: [min, max, step],choiceortoggle: true, andvalue,format),frame,base,label,class. A slider, switch or toggles changing a plot or chart, readout,statsand caption;frameruns in the browser when a"use client"module exports it, elsepackcomputes every frame.Stepper:captions,captionsFirst,start,label,class,onStep,restartOn(back to step 1 when it changes).View:name,param,backfor a static view per record;recordsandrenderfill one view from data.Tree:items,row,cols,head; typesTreeItem,TreeRow.Table:rows,cols(eachhead,value,format,link,note),state.Question:name,legend,options,multiple,checked,answer(a self-check that marks the choice and opens the explanation written inside). Withoutoptionsit takes a written answer.
Frontmatter keys: title, lang, labels, reply: true, scheme: light | dark.
Math: $...$ in a sentence and $$ on lines of their own render to MathML when the page packs (kit math.css, with the formula sheet pattern).
Slots: Markdown between a client component's tags is its children, rendered once and placed with {children}.
CLI: pack page.mdx -o out.html; kit lists components and features, kit <Name> documents one, kit page the page conventions, kit labels prints every kit text as a frontmatter labels block to translate, kit types > tsconfig.json then bun check typechecks the page's .ts and .tsx with nothing to install.
Removed
- HTML as
packinput, and thekit:file references in a page. kit <file>for the kit scripts that components now write for you (views.js,steps.js,charts.js,plot.js);kit link,kit replyandkit keepdocument the attributes that stay.var kitLabelsin a page script; use the frontmatterlabels.
v0.8.2
Lessons from a blind comparison round, and wide blocks centred beside the contents rail.
New public class names: num (inside .compare). Changed: .wide beside the rail now grows equally to both sides, and on windows wider than about 1360 px the rail moves further into the left margin to make room.
Kit
.widebeside the contents rail is centred on the prose column and grows by the same amount on both sides, up to 88rem, never past the rail's gap or the window gutter. The view's breadcrumb row stays with the column (#48 follow-up)..compare .numaligns a numeric column to the end with tabular digits; on phones, in the stacked layout, the value starts beside its label (#54).- A page's
<textarea>gets the kit's input border, radius, padding and background, spans its column and resizes only vertically. kit theme.cssstates the.comparemarkup contract (row headers,data-label,num,data-state) with a one-row example.
Guide
- Study pages: a question with a bounded answer offers a few plausible choices as radios, an explanation gets a
<textarea>, each with an id sokit:reply.jskeeps it, and a<details>after them holds the explanation. Headings there ask the question rather than giving the answer away (#7). - What no control changes (table rows, item lists, a view's body) goes in the HTML as you write the page, so it shows before scripts run, prints, and keeps the layout still.
Examples
- The café example writes its comparison rows in the HTML and gives its reply radios ids. All examples are repacked.
v0.8.1
Record-heavy pages, from an 86-record estimate built with v0.8.0.
New public class names: tree-head, toolbar, legend. Changed: --tree-cell is replaced by --tree-cols.
Kit
.widebeside the contents rail reaches into the right margin up to the window's gutter; prose keeps its measure (#48).- Trees:
--tree-colssets one width per number column (without it, 5rem each, up to six); an optional.tree-headrow names the columns, aligned with the cells; on phones a row's numbers move to a second line in the same columns (#49). .toolbarlays out a records section's view switch, search and filters: one compact row on desktop, a clean grid on phones. Layout only; the page owns state (#50).- Category markers:
.chipwith--swatchshows a square in the category's colour before its word, and ap.legendkeys the categories once (#52).
Pack
- The kit's CSS sits in
@layer kit, so a page's own CSS always wins over it without specificity fights. packwarns when the page's own CSS styles a class the kit also styles, naming the class, the page rule and the kit part (#51).
Guide
- A tree row carries only what readers compare across rows; the rest goes into the opened item without repeating it.
- A marker encodes a category readers scan across rows and is never decoration.
v0.8.0
Pages built on many records, from a project plan that passed check but failed its reader.
Kit
.tree: nested records as nested<details>, one summary line per level with aligned numbers, Expand all for every level, links that open the branch they name, highlights on closed branches, open in print (#40).data-navgives a section a short contents-rail label; navigation names wrap instead of being cut off (#41).- A plain
#anchorinside a view keeps that view, also on Back and reload (#31). - The Reply and Clear controls sit in a strip at the top of the window and never cover content (#20).
- Closed
<details>print open; Collapse all shows no stray minus.
Guide and SKILL.md
- Regrouped by the page's situation: the reader, many records, explaining, words and trust, showing.
- New: orientation lines under the headline for readers new to the subject; story in front with every record in one complete list behind; hierarchy as a tree; every item says what it is, why and what done means; use every source that describes the subject; show the subject itself, with an inline-SVG recipe.
Repo
- README states the project's priorities: the reader's understanding first, then agent ease and readable source, then portability.
- Examples repacked and media re-recorded; the media recorder leaves room for the reply strip.
v0.7.2
Guide and SKILL.md, from a plan page that passed check but failed its reader:
- A page about a set of items (tasks, costs, findings, requirements) keeps one complete list of them on the main page, grouped with subtotals, each row opening its view.
- Items keep the source's order; sort by value only where the ranking is the answer. Removes the "largest first" advice for views.
- What a number rests on (method, assumed values, measured analogue, confidence) is content and stays beside the number; a method the reader must defend is its own section.
- With a decider and a team on one page, start from the decider's questions.
- Finish by checking each of the main reader's questions is answered without hunting across views.
v0.7.1
- The kit's own buttons, aria names and status messages follow the page's language through one
kitLabelsobject, set anywhere in the page's top-level scripts; English stays the default. Replaces the stepper'sdata-labels, which now only warns (#4). - charts.js resolves colour tokens in data arrays that mix numbers and objects (#34).
- Guide and SKILL.md: write about the subject, not the conversation that produced the page (no process history, earlier drafts or quoted remarks; a figure the requester mentioned is an input, a control when the result depends on it). Flag the page's own reasoning once, beside the claim, only where the reader would act on it.
- Examples follow the same rule; README documents navigation and labels.