Skip to content

Releases: abdul-hamid-achik/tuimark

v0.4.5

Choose a tag to compare

@github-actions github-actions released this 21 Oct 04:44
c8ef6b0
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.4.5

Docs: https://tuimark.dev

Static props and one default slot for templates (0.4c), and Kit v2 built
on them. A <template> declares props="a b", a <use> passes them as
attributes, and {$a} reads them in the template's body. A <slot/> in a
template marks where the content of a <use> goes; that content is built
where it is written. Documents that use neither, and every document that
writes version, behave as in 0.4.4.

Added

  • Props.
    • props="a b" on a <template> declares static props.
    • A <use> passes them as attributes (title="Deploy"), and {$a}
      reads them in the template's body: in attribute values, in the text of
      a text, button or column, and in the other places the composition
      guide lists.
    • A prop is literal text, substituted once per copy. When an attribute's
      whole value is a prop the use does not pass, the attribute is left out.
      Anywhere else, an unpassed prop is the empty string.
    • Where a prop cannot stand (an id, a path, each, a <use>'s
      template, inside {…}), or outside a template, the error is V003.
      A prop the template does not declare is V002 on the <use>.
  • The slot.
    • A <slot/> in a template marks where the content of a <use> goes.
    • The content is built where it is written: the use doesn't scope its
      ids, its paths read the store and the aliases in scope there, and its
      includes resolve next to its own file. The use's class never reaches
      it.
    • A <slot/> inside a use's content in a template forwards the outer
      use's content.
    • A template has at most one slot (a second is V019).
    • Content toward a template without a slot is V013
      <template id="T"> has no <slot/>. Loose text in a <use> is V013.
    • A misplaced slot is V001: outside a template, brought by an
      <include> in a use's content, inside a list <item>, or where
      content repeats per row.
    • <slot> in a document that writes version is V001 with the
      requires hint.
  • Tools.
    • tuimark ir:
      • a copy's <slot/> is a slot node holding its fill;
      • templates keep props, and uses in a template body keep their
        content;
      • every placed node has from.
    • tuimark inspect: via lists slot entries
      (via <slot/> of <use id="U" template="T"> FILE:LINE:COL).
    • tuimark fmt: formats a use's content and <slot/>.
    • tuimark lsp:
      • completes a use's props, {$name} in a template, and slot;
      • hover and definition lead from a prop to the template's props.
    • tuimark catalog: lists slot, props on template, and
      "open_attrs": "props" on use.
  • Kit v2.
    • The kit's templates take literal text as props and, where a component
      has a body, content through <slot/>.
    • New templates:
      • k-card: a titled panel; its body is the use's content.
      • k-appbar: brand and title props; the right side is the content.
      • k-kv: one key/value row,
        <use template="k-kv" label="Owner" with="svc.owner as v"/>.
    • k-alert, k-empty and k-confirm take content too.
    • Every template takes a style prop for its root, which a rule cannot
      name unless the template gives it an id (the overlays' U__dialog,
      the log's U__view).
    • k-stat takes min and max, the sparkline's range.
    • k-help lists its keys in a scroll (U__keys), which takes the focus,
      so a long keymap scrolls without a height rule in the app.
  • Docs.
    • The composition guide has sections on props, the slot, and when to
      use props, with, or the slot.
    • README's "Language notes" and AGENTS.md cover them too.

Changed

  • Messages. "Tuimark has no slots" is gone from every message.
    Content in an <include> now says to wrap it with a template and a slot.
    The V002 of an attribute a <use> does not take now ends with "or
    declare it in the template's props" when the name could be a prop.
  • Help overlay. The kit's help overlay shows "? close help" as its own
    line, above the list of keys, and the kit's .k-help keymap row has no
    label any more.
    • A <hints scope="all"> in a document that includes the kit without
      using the help no longer shows "? close help".
    • A hint bar under the open help no longer repeats it.
    • The overlay itself looks as before.
  • Kit v2: literal text is a prop, not data.
    • These templates and props:
      • k-alert: title, icon, text;
      • k-stat: title;
      • k-meter: label;
      • k-empty: icon, title, text;
      • k-field: label, placeholder, help;
      • k-palette: title, placeholder;
      • k-help: title.
    • The members they replace are no longer read. A use that still passes
      them only through with shows no title or label, with no diagnostic.
      The kit README's "Upgrading from kit v1" section has a migration table.
  • k-empty. Its call to action is the use's content: your button,
    your id, your action. The kit no longer has empty_create or
    U__create.
  • Palette helpers. The kit's palette helpers (Go and TypeScript) no
    longer write a default title and placeholder into the store.
  • Examples.
    • examples/containers includes kit/kit.tui and uses its templates.
      Its detail tab is 45 lines instead of 72, and the whole document 209
      instead of 274.
    • examples/kit-gallery shows "v2".

v0.4.4

Choose a tag to compare

@github-actions github-actions released this 20 Oct 14:37
891d164
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.4.4

Docs: https://tuimark.dev

Kit v1: the component kit rebuilt on the composition of 0.4.3. A document
includes kit/kit.tui once and uses its templates with <use>, instead of
copying each component's markup. The language and the runtime do not
change: only the kit's files, its helpers, and the documents that load the
kit's stylesheets do.

Added

  • kit/kit.tui: the kit as one fragment to include. It loads the kit's
    stylesheets, holds the keys of its overlays, and has eleven templates
    (k-alert, k-stat, k-meter, k-empty, k-field, k-breadcrumb,
    k-log, k-toast, k-confirm, k-palette, k-help) used as
    <use template="k-…" with="path as x">, with a variant as a class on the
    <use>, labels from data, and ids scoped by the use (ID__part).
  • The kit's dialog helpers take button labels: Request.Yes/Request.No
    (Go) and yes/no (TypeScript); empty means Confirm and Cancel, and
    both labels are written on every open, so an earlier question's labels
    never stay.
  • The /kit page shows each template's source next to the document that
    uses it; the kit guide shows how to include the kit, pass data with
    with, and pick a variant with class.

Changed

  • Every kit/components/*.tui includes ../kit.tui instead of loading the
    two stylesheets; the eleven components that have a template now show it in
    use, and the markup of the ten class-only components is otherwise
    unchanged.
  • The templates read from data the text the old component documents wrote
    as literals: an alert's title, text, and icon, a stat's title, a meter's
    label, a field's label, placeholder and help, the empty state's icon and
    call to action, the log's title, the palette's title and placeholder, the
    help's title, and the dialog's button labels. Data written for the old
    documents shows these blank, and leaves out the optional ones (the
    alert's icon, the empty state's button).
  • The palette helpers write a default title (Command palette) and
    placeholder (type a command…) when the store has none.
  • The confirm dialog reads body, yes, and no (it read message, with
    fixed labels), and the dialog helpers write body, yes, and no
    instead of message. .k-dialog takes the size of its content, up to 60
    columns (it was 52 wide), so every dialog with that class changes,
    examples/containers' included.
  • The confirm dialog's yes button is k-btn primary, painted red in a
    danger dialog (the variant or d.danger); it was k-btn danger in
    every dialog.
  • The confirm dialog's y and n keys have no label, so no <hints> lists
    them: the dialog's buttons name the choices, and the keys work while it
    is open.
  • A kit field's input is FORM__FIELD__input, and the log's scroll is
    ID__view. The kit's form helpers route the new ids and focus
    FORM__FIELD__input on a failed submit.
  • The alert's icon takes its color from the alert's variant, so the
    .k-alert-icon.info|ok|warn|danger rules are gone; the meter's bar also
    takes the meter's variant (its .k-meter-bar.ok|warn|danger flag rules
    stay).
  • examples/kit-gallery is built from the kit's templates. Its help overlay
    no longer lists y confirm and n cancel, and its hint bar no longer
    shows them while the confirm dialog is open.

v0.4.3

Choose a tag to compare

@github-actions github-actions released this 20 Oct 05:49
80e65ff
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.4.3

Docs: https://tuimark.dev

The 0.4b release. It extends the current language, the language of a
<tui> without version, in three directions:

  • reuse and split markup: <include> and <fragment> put a document in
    several files, and <template> and <use> stamp a piece of markup with
    its own ids and its own data;
  • form controls: <checkbox>, <select>, radio groups (single), a
    spinner (<progress indeterminate>), and {path} in a button's label;
  • defaults for people coming from the web: content-sized col and row,
    an esc that closes a modal, a modal that traps the keymap, a focus look
    on buttons.

There is no version="4": writing it is V003, and the document is read as
version="1". Tools that need a number for the current language use 4. A
document that writes version="1", "2", or "3", and the spike <app>
root, keeps exactly the meaning, layout, diagnostic codes, and dumps it had
in 0.4.1. A tag, attribute, or {path} that 0.4.3 adds is V001, V002,
or V003 in such a document, with a message ending in (requires a <tui> without version) (the hint for <template> and its kin used to say Tuimark
had no components); an include's file is never read there.

Changed — versionless documents

A <tui> without version changes layout and keys with 0.4.3. To keep
0.4.1's behavior, write <tui version="3">; nothing else in the document
needs to change. To see what a document gains or loses, copy it with
version="3", run tuimark dump --format json on both at 80 and 120
columns, and diff the results. Measured this way on the 15 examples that
write no version, at five sizes, none changes its base layout, because each
writes the sizes it relies on; with a modal open, some show the focus look,
the trapped keymap's shorter hint bar, or content-sized hints. Documents that
leaned on the old defaults change in these ways.

Layout.

  • col and row are content-sized (auto on both axes) except as
    direct children of a screen, a modal, or a tab, where they stay
    1fr by 1fr. A sidebar col beside a body col is as wide as its text,
    and the body needs width: 1fr to take the rest; panes that are wider than
    their row push the last ones out (L009), and a wrap: wrap text in a
    content-sized pane is measured unwrapped. The col and row children of an
    each container take their content size instead of equal shares, justify
    acts in containers that 1fr children no longer fill, and a % child of a
    content-sized container is L002. A header or status row written directly
    in a screen is still 1fr, so give it height: 1. box is unchanged (it
    was never 1fr on its main axis).
  • The sibling-fr rule applies to viewports only. An unsized sibling of
    an fr child becomes 1fr only when it is a scroll, a list, a table,
    or has overflow: scroll; a text, button, box, tabs, or hints next
    to an fr pane stays at its content size. A filter row above a list in a
    tab, a screen, or a modal is 1fr and splits the space with the list:
    give it height: 1.
  • fr meets min-* and max-* as CSS does. A child that a max clamps
    (or a min lifts) is frozen at that size, and the other fr children share
    what it frees: three 1fr columns in 40 cells, the first with max-width: 10, are 10, 15, and 15, where a pinned document gives 10, 13, and 14 and
    leaves 3 cells empty. Table columns keep their old split.
  • modal and hints are content-sized. A modal with no size is as big
    as its content, wide enough for its title, capped by a written max-width
    or max-height and otherwise by 80% of the screen (a pinned document
    gives 80% by 80%). A hints is as big as its items along their axis and
    drops the items that do not fit, with no diagnostic, so a status row of a
    text, a spacer, and <hints/> keeps the text and the hints whole.
  • An input is max(width(placeholder) + 1, 20) columns wide when it
    has no width, whatever its value, so it no longer grows while you type.

Keys and focus.

  • esc closes the top modal that has no on:escape, by writing false
    at its open path (its bind path when it has no open); the next frame
    fires on:close and gives focus back. A modal whose open is true,
    false, or !path, or whose path a Derive computes, stays open, because
    there is nothing to write: use one of these, or on:escape, for a dialog
    that must not be dismissed (the game-over modals of snake and tetris).
  • A modal traps the keymap. While one is open, a keymap row fires only
    when its effective when matches the focus chain up to the top modal, so a
    q quit row without when no longer quits under a confirmation, and a
    ctrl+c row without when gives way to the built-in quit. tab,
    shift+tab, ctrl+c, and the keys the focused widget takes still work, and
    <hints/> shows only the rows that fire. A key that must also work under a
    modal is repeated in <keymap when="modal">, a group whose rows all carry
    that when.
  • A tabs is focusable without focusable="true": tab reaches it,
    left and right switch the tab, and focus stays on the strip when the
    active tab changes. focusable="false" turns it off. Specs that count tab
    presses change; a screen focus= keeps the old initial focus.
  • A focused button, checkbox, or select is reversed by the built-in
    sheet (button:focus, checkbox:focus, select:focus { reverse: true; }).
    A document whose buttons have another focus style adds reverse: false to
    it; the kit's .k-btn:focus now does.
  • ctrl+i, ctrl+j, and ctrl+m in a keymap are V003, once per
    token, because a terminal delivers them as tab and enter and they never
    matched. Write tab or enter. Pinned documents accept them as before.

Messages and output.

  • L002 and L009 name the fix. L002 under a col or row ends with
    (width: 1fr fills it) (or height), and L009 on a content-sized col,
    row, or box says that the child is sized by its content and does not
    shrink, and to give it width: 1fr (or flex: 1), or to write overflow: hidden to keep the cut.
  • tuimark ir prints "version": "0.4" for a versionless document (it
    printed "0.3"), validated by the new schema/ir.v0.4.json, and tuimark inspect ends with a source line (--json: a source member) naming the
    element a node was built from. Pinned documents print neither.

Added

  • <include src> and <fragment>. <include src="parts/forms.tui"/>
    places the elements of another file where it stands: directly under
    <tui>, in a <screen>, in a container, or in a <keymap>. src is
    relative to the file that writes the include. The file's root is
    <fragment>, which takes no attributes and holds any number of elements,
    <template>s, a <style>, a <keymap>, or further includes. Ids, styles,
    and data are shared and the result is checked as if written where the
    include stands; a diagnostic about a placed element ends with , placed by <include> at FILE:LINE:COL. A fragment is named by its path relative to the
    document, and the same fragment may be included twice (its ids then repeat:
    V004). Failures insert nothing and the rest of the document is still
    checked:

    • an include cycle is V006, judged by file identity, so a symbolic link
      cannot hide one; so are a file outside the file system LoadFS was
      given, one that cannot be read, and one that is not a regular file of at
      most 64 MiB;
    • a stylesheet, or a file whose root is not <fragment>, is the new V020.

    A fragment is not a document: dump, play, preview, ir, and
    inspect print V005 for it ("check it through the document that includes
    it") and exit 2, while validate checks it on its own (ok (fragment checked on its own), with or without --data) and fmt formats it.

  • <template> and <use>. <template id="field"> holds a piece of
    markup that is never drawn by itself, and <use id="email" template="field" with="form.email as f"/> is replaced, when the document is read, by a copy
    built where the use stands. A template is a direct child of <tui> or
    <screen>, in the document or in a fragment, and may come after its uses.
    A <use> is not a node, so it takes no space and holds no content (no
    slots).

    • Scoped ids. The copy's ids are prefixed with the use's id and two
      underscores: the input of <use id="email"> is email__input, and a
      use inside a template adds its own prefix (billing__street__input).
      Outside the copy (to=, when=, focus=, #id selectors, focus:
      steps, an event's source) you write the scoped id; inside the template
      #x means the element of the same copy. A use needs an id when its
      template holds any (V012), and naming an unscoped id from outside is
      B005 with the scoped form in the message.
    • with="path as alias" makes the alias stand for the path inside the
      copy, so {f.label} reads form.email.label and bind="f.value" reads
      and writes form.email.value; clauses are separated by exactly , . A
      copy also reads the aliases in scope where its use stands (a list row's
      each alias). A with path that is missing is one B003 at the use and
      nothing more from the copy, and a flag read through an alias that stands
      for an object (if, hidden, disabled, open, class:NAME) is null
      when the member is missing, so a field shows an error only when the host
      wrote one.
    • Guards and classes. if and hidden on a use are read where the use
      stands and are decided per row in a list <item>, an each container, or
      a table. A stati...
Read more

v0.4.2

Choose a tag to compare

@github-actions github-actions released this 19 Oct 11:52
7ea9734
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.4.2

Docs: https://tuimark.vercel.app

A patch on 0.4a with no change to the language. Tuimark ships on npm as
@thelacanians/tuimark (the TypeScript SDK and a tuimark command that
downloads the verified release binary), the component kit gains copyable Go
and TypeScript host helpers, examples/ gains a container-engine dashboard
built on the kit, and the runtime builds for js/wasm for the website's
playground.

Added

  • The npm package @thelacanians/tuimark (sdk/ts): the SDK and the CLI
    in one package. import { spawnHost } from "@thelacanians/tuimark" is the
    TypeScript client that was @tuimark/host (never published), and its new
    tuimark command (npx @thelacanians/tuimark new my-app) runs the Go
    binary of the release with the same version, with the same arguments,
    stdio, exit code, and signals (and fd 3 and fd 4 of tuimark host when a
    parent starts the installed command directly; parents should prefer
    spawnHost, or run the binary themselves). The launcher is plain JS for
    Node 18 or later and Bun, with no dependencies and no postinstall script.
    It finds the binary in TUIMARK_BINARY, else the cache ($XDG_CACHE_HOME
    or ~/.cache, ~/Library/Caches, %LOCALAPPDATA%, then
    tuimark/<version>/<os>_<arch>/tuimark), else it downloads the release's
    tuimark_<version>_<os>_<arch>[.exe], checks its SHA-256 against a table
    built into the package from the release's checksums.txt (a mismatch is
    discarded and never run), and caches it; a cached binary is checked against
    the same hash on every run. TUIMARK_RELEASE_URL points it at a mirror,
    checked by the mirror's own checksums.txt and cached apart.
    spawnHost uses the same lookup when bin is not given, and
    resolveBinary is exported.

  • Raw binaries in the release. GoReleaser now also uploads
    tuimark_<version>_<os>_<arch>[.exe] (Go's own OS and arch names) for the
    six platforms, listed in checksums.txt, next to the archives. The Homebrew
    cask still uses only the archives.

  • npm publishing in release.yml. After GoReleaser, the npm-pack job
    (no OIDC token) builds the package at the tag's version, embeds the six
    checksums, and checks the release has the six binaries, and the
    npm-publish job (GitHub environment npm, which allows only v* tags)
    publishes it with npm trusted publishing and provenance. Run by hand from a
    release tag ("Use workflow from"), the workflow publishes that tag's
    package.

  • The Tuimark kit (v0). kit/ is a copy-paste component kit in the
    spirit of shadcn: two stylesheets (theme.tcss, the dark and light design
    tokens on top of the runtime's ten required ones; kit.tcss, the k-
    component classes) and 21 small documents under kit/components/ (badge,
    button, card, alert, app bar, status bar, key hints, confirm dialog, form
    field, empty state, data table, menu, tabs, labeled progress, stat, toast,
    command palette, help overlay, split pane, log panel, breadcrumb), each with
    a contract comment, markup to copy, and a sample. Classes are k-<component>
    and k-<component>-<part> with short modifiers used only next to them, ids
    are instance__part, and actions are component_verb. It adds no tag,
    attribute, or property.

  • examples/kit-gallery: every component in one app on five tabs, with a
    Go host that routes by the prefix of an event's source, a mock.json, and
    tests that hold the kit's conventions to every document.

  • More fuzz targets (internal; task fuzz runs each briefly, outside
    task verify and CI): the document parser (diagnostic codes, positions,
    determinism, fmt keeps the diagnostics), the binding, key-token and
    size grammars, the stylesheet cascade, play step parsing, and a
    whole-runtime render of any document or any stylesheet (grid shape,
    snapshot independence, no hang). About an hour of local fuzzing found no
    crash.

  • Kit host helpers (kit/go, kit/ts). The behavior markup cannot
    express, as files you copy into your project and own, not a
    public package: route (an event's instance and part), toasts (a capped
    queue that expires), filter (substring or fuzzy, into a store path with
    counts), sortpage (column sort cycling with arrows, and pages), form
    (per-field rules writing forms.<form>.<field>.error and
    forms.<form>.valid), dialog (Confirm, routed by instance), palette
    (a command registry), async (<path>.loading, .error, .data) and
    format (bytes, durations, relative time, percentages, thousands). kit/go
    is a nested module named tuimarkkit (no dot, so go get cannot fetch it)
    whose tests drive the real kit components; kit/ts is typed against
    @thelacanians/tuimark. Both run in CI and in task verify. For the helpers to be
    sound, two kit contracts moved: a form is named by the id prefix of its
    inputs (profile__name) with its state at forms.<form>.<field>.{value, error}, and the palette's input fires a palette_query action as the query
    changes (a host without Derive, such as a TypeScript parent, recomputes
    found there).

  • examples/containers: a container-engine dashboard built only from the
    kit, over a simulated engine (a seeded state machine on an injected clock; no
    Docker). A filterable, sortable, multi-select table with <column priority>,
    a detail pane (overview card, CPU and memory sparkline pair, nested tabs for
    a stick="bottom" log, the environment and the ports), images, volumes and an
    events log; stop, restart and remove (a danger dialog) and the prunes through
    the kit's confirm dialog, a command palette filtered by Derive, a help
    overlay, toasts, a status bar and key hints. It ships sample.json and
    mock.json (generated snapshots, kept in step by a test), tests for the
    view at 40, 80 and 120 columns in both themes with every tab and modal open,
    the kit's conventions and the Play flows, and a -demo COLSxROWS mode that
    the docs site turns into an animated SVG. loadfs_test.go now gives any
    example that loads ../../kit the repository root as its file system.

  • A js/wasm build target, for the website's playground. The core packages
    (the root package and internal/**) now build and pass their tests for
    GOOS=js GOARCH=wasm; the one blocker was syscall.SIGWINCH in
    internal/host/resize_unix.go, now retagged unix (with resize_other.go
    for everything else). cmd/tuimark-wasm is the entry that compiles to
    tuimark.wasm (about 1.2 MB with brotli): a private, unversioned JS API
    (open, render, step, validate, reload, close) over the same
    runtime, whose frames are the CLI's dumps (tests compare them byte for byte
    for every example and kit component). It is not a command, is not published,
    and promises no compatibility. task wasm builds it, task wasm-test runs
    the core tests under js with Node, and task verify checks its size budget
    and a Node smoke test. The CLI and Run are not part of the js target.

Changed

  • @tuimark/host is now @thelacanians/tuimark, in tuimark new --ts
    (whose package.json depends on ^<the running version>), examples/host-ts,
    the generated AGENTS.md, and the guides. The package version is the Tuimark
    release it runs (it was 1.0.0), and sdk-ts-publish.yml and its sdk-ts-v*
    tags are gone.

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 19 Oct 02:36
ce3e8dc
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.4.1

Docs: https://tuimark.vercel.app

A patch on 0.4a. version on <tui> becomes optional, and examples/
grows by thirteen apps: four tools, four animated apps (Snake among them),
and five more games, with animated demos of nine of them on the docs site.

Changed

  • version on <tui> is optional. A <tui> without it is the
    current language, version="3": it accepts the whole vocabulary, and
    L008/L009 and the dump's clipped apply to it. V014 is never
    reported any more; its row stays in the catalog, marked retired. A
    document that writes version="1", "2", or "3" keeps exactly the
    meaning it had, and any other value is still V003 and read as
    version="1". tuimark new, the examples, and the docs write no
    version, except the SPEC fixtures (examples/inbox, examples/monitor)
    and the examples that are version="1" on purpose. The language server
    completes a document without version with the current vocabulary.

Added

  • Four example apps in examples/, each a document, a
    stylesheet with dark and light palettes, sample data, a mock.json for
    tuimark run --mock, and a Go host on the 0.4 API with its own tests:

    • logs: a live log viewer. Lines stream in with Every, the view
      follows the end with <scroll stick="bottom">, and Derive keeps the
      filter and the counts. -follow FILE tails a real file.
    • tasks: a kanban board with three lanes, multi-select, a form modal,
      and a delete confirmation. -file keeps the board in a JSON file.
    • gitui: a git client with status and a diff preview, the log with
      each commit's detail, and the branches. -repo PATH reads a real
      repository, read-only unless -write is also given.
    • http: an HTTP API client with a collection, a request editor, and
      the response's body and headers. It runs against an in-process fake
      API unless -base URL is given.
  • examples/README.md lists every example and what it shows. The docs
    site's Examples page has screenshots of the four new apps.

  • Four animated example apps, built the same way:

    • snake: a playable Snake whose Every tick re-arms faster as the
      level rises; -autopilot plays it.
    • pipeline: a CI run with parallel jobs, spinners, progress bars, the
      selected job's log streaming into a <scroll stick="bottom">, a flaky
      test, retry, rerun, and cancel.
    • music: a music player with no audio, with a library, a queue,
      albums, and a spectrum visualizer drawn by a six-row <sparkline>.
    • pomodoro: a Pomodoro timer and a clock in big block digits drawn
      from one derived string, with phase colors from class: guards.

    Each has a -demo COLSxROWS flag that replays a scripted session on a
    fake clock and prints its frames as JSON lines.

  • Five more games, built the same way and with the same -demo flag:

    • chess: legal move generation checked by perft, SAN, UCI, FEN and PGN,
      an alpha-beta engine with six levels that thinks in a goroutine, four
      modes (against the computer, two players, computer against computer,
      review), clocks, undo and redo, hints, promotion, resign and draw
      claims, and a board of 64 static clickable cells.
    • tetris: SRS rotation with wall kicks, a 7-bag, hold, a ghost piece,
      lock delay, a line-clear flash, T-spins, combos, and an autopilot.
    • minesweeper: beginner, intermediate, expert and custom boards, a safe
      first reveal, flood fill, chording, flags and question marks, and a
      flag mode for the mouse.
    • 2048: a 3×3 to 6×6 board, undo, win and game-over modals, highlights
      for merged and new tiles, and an expectimax autopilot.
    • sudoku: a generator whose puzzles have one solution and are graded by
      the solving techniques they need, notes, hints, a check, conflicts,
      undo and redo, and a clickable number pad.
  • The docs site's Examples page shows those nine as animations: the
    generator renders the -demo frames as one CSS-animated SVG per theme
    (only the cells that change are stored per frame), and each has a pause
    button. Readers who prefer reduced motion start on the last frame.

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 16:58
af76033
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.4.0

Docs: https://tuimark.vercel.app

0.4a is the "frontend loop" release. It adds the tools that let you build
a terminal app the way you build a web front end:

  • see the document live and edit it while it runs (tuimark run);
  • start a project from a template (tuimark new);
  • get diagnostics and completion in the editor (tuimark lsp and a VS
    Code extension);
  • drive the view from TypeScript (@tuimark/host);
  • keep timers and computed values out of the markup (Every, Derive).

The vocabulary does not change: version="3" is still the newest, and
every document keeps its meaning and its dumps.

Added

  • Go API.
    • RunContext(ctx, w) and RunWith(ctx, w, RunOptions{OnError}) run
      the app under a context. A cancellation ends the session as a quit
      and restores the terminal.
    • OnError decides whether an error from a handler, an Every, or a
      Derive ends the session or is shown and survived.
    • Quit() ends the active session from any goroutine.
    • Every(d, fn) runs fn every d on the loop's goroutine, as one
      Batch with one redraw. Late ticks are coalesced, and stop is safe
      from anywhere.
    • Derive(path, fn) keeps a computed value (a filtered list, a count)
      in the store. It is recomputed inside every write, so nothing ever
      sees a stale value, and it is read-only for every writer: a widget
      bound to it does not change.
  • tuimark run FILE shows a document live in the terminal, with no
    Go and no parent program, and reloads it in place when you save.
    • Host actions are answered by run itself: --mock FILE.json gives
      each one fixed store writes, so a prototype can react without a
      backend.
    • F12 (or --strip) shows a strip of the last events, and
      --events FILE.ndjson writes them as the host protocol's event
      lines. A secret input's value is redacted in both.
    • A panel lists the document's diagnostics, or why a reload failed.
    • A reload keeps the store, the focus, cursors, scroll offsets, inputs,
      and the active tab for every node whose id and tag are unchanged.
    • Two ctrl+c within one second always end the session.
  • tuimark catalog prints the whole vocabulary as JSON: tags,
    attributes and their keywords, CSS properties and values,
    pseudo-classes, media features, actions, key tokens, and diagnostic
    codes, each with the version that introduced it. The format is
    described by schema/catalog.v1.json.
  • tuimark new NAME [--go | --ts] writes a project that validates
    cleanly at 40, 80, and 120 columns under both themes: app.tui,
    theme.tcss, sample.json, and an AGENTS.md briefing for coding
    agents (tuimark agents --project). --go adds a main.go host and
    --ts a parent.ts built on @tuimark/host. The Go module is named
    after the directory, and a name Go refuses is rewritten: log or
    time (a standard-library root), std, cmd, and a Windows device
    name such as aux get -app, so tuimark new log --go builds.
  • tuimark lsp is a Language Server Protocol server for .tui and
    .tcss.
    • Diagnostics are exactly validate's. They are routed to the
      stylesheet they belong to, and a sibling sample.json is used as the
      data.
    • Completion, hover, go to definition, and formatting with tuimark fmt.
    • It reads only regular files, at most 64 MiB each (a sample.json,
      a .tui or a <style src> that is a link to /dev/zero or
      /dev/stdin, or a FIFO, counts as absent). Its diagnostics for one
      document never arrive out of order, and a save or a watched file
      refreshes those of a stylesheet nobody has open.
  • VS Code extension (editors/vscode): TextMate grammars that mark
    anything outside the closed vocabulary, snippets, and a client for
    tuimark lsp. Without the binary, highlighting and snippets still
    work. The server starts only in a trusted workspace, since a workspace
    can set tuimark.path, and only from a binary whose version prints a
    tuimark version.
  • @tuimark/host (sdk/ts), the TypeScript client of the host
    protocol, for Bun. spawnHost starts tuimark host, and on, set,
    batch, get, and exited do the rest. It follows every rule for
    parents: signal forwarding, SIGTERM then SIGKILL, and never
    touching the terminal. A SIGTERM or SIGHUP is forwarded after a
    1.5 s grace, and only if the child is still running, so a signal sent
    to the whole process group does not reach the child twice. Messages of
    any size go through fd 3. examples/host-ts now uses it.
  • TUIMARK_LOG writes a {"ev":"rounds","queued":N} record when the
    lifecycle rounds hit their cap.

Changed

  • Faster frames for big lists. Moving the cursor in a 20 000-row
    list now takes about 5.5 ms per key instead of about 120 ms (Apple M5).
    A one-path Batch over a 20 000-row store, with the frame it draws,
    takes about 5.5 ms instead of 64 ms. Six things do it:

    • an unchanged frame is not rebuilt;
    • a list's rows are reused while the data they read is unchanged;
    • equal rows share one computed style;
    • a frame's rows are built in memory kept from the frame before last;
    • a Batch copies only the parts of the store it writes;
    • layout measures a list's height once a frame, and checks each box
      as it places it instead of in one more pass.

    Dumps are byte for byte the same. task bench prints the numbers.

  • Lifecycle events past the 8th round are kept, not dropped. They are
    dispatched first at the next settle, whatever causes it: a key, a
    resize, a tick, or a Play step.

  • tuimark validate says when it did not check bindings. Without
    --data the text output ends with ok (bindings not checked: no --data), and the JSON output has a data member (the path, or
    null).

  • tuimark host bounds fd 3 lines. A line of 64 MiB or more ends the
    session with {"type":"error","error":"fd 3 line of 64 MiB or more"},
    instead of growing without limit. tuimark mcp already stopped there.

Fixed

  • A list or table whose key is an object or an array no longer
    shares that value between the row and the paths the runtime writes it
    to (bind, checked) or the events it fires: a Set through one of
    them, or a handler changing its event, changed the row too.

v0.3.2

Choose a tag to compare

@github-actions github-actions released this 17 Oct 08:19
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.3.2

Docs: https://tuimark.vercel.app

A patch: fixes and friendlier diagnostics from a full review of 0.3.1.
No vocabulary changes; documents keep their meaning and their dumps,
except that a few invalid ones now say so.

Changed

  • Diagnostics suggest a fix. An unknown tag, attribute, CSS property,
    or theme within two edits of a known one ends its message with ; did you mean <list>? (or "width", naming the version a newer name needs).
    A common HTML name gets the Tuimark way to say it instead: <div> → use
    <box>, <col>, or <row>; <span>/<p> → <text>; <checkbox> →
    a <list> with checked=; <include> → there are no components or
    includes. The codes are unchanged.
  • tuimark play --input explains a token that fails right after a
    text: or paste: step: --input splits steps at spaces, so text with
    a space needs --script.
  • App.Dump refuses a size above 1000 in either dimension, the limit
    Play and the CLI already had, instead of trying to allocate the grid
    (a huge size panicked).
  • The viewport key clamp of 0.3.1 applies to every document version.
    It fixed an up lost after end in the same terminal read, so it is
    kept for version="1" and version="2" too.

Fixed

  • @media error recovery. A selector with no { inside an @media
    block no longer swallows the block's closing }, which made every rule
    after the block conditional on the media query.
  • Selectors that can never match are V003. #1, .9x, and .-x
    name an id or class no element can have (§7's grammar), in stylesheets
    and in keymap when alike.
  • A leading + is not a cell count. gap: +3, padding: +1,
    gap="+3", and @media (max-cols: +80) are V003.
  • tuimark fmt keeps the XML declaration (<?xml version="1.0"?>)
    instead of dropping it.
  • Clearer messages. V014 names version="3"; a bare & followed
    later by a ; says "bare '&'" instead of naming a garbled entity; a
    DOCTYPE or processing instruction after the root gets its own message;
    text inside an unknown tag no longer adds a V013; box: says the
    pseudo-class is empty.
  • tuimark mcp accepts valid JSON with numbers beyond a double
    (1e400, a 400-digit literal): such a line was answered -32700 (parse
    error), although only a line that is not JSON gets it. An id is echoed
    as written.
  • tuimark fmt --write --check is a usage error instead of silently
    running only the check, and tuimark version refuses arguments.
  • tuimark test rejects a manifest entry whose name is empty, not a
    single path segment, or already used (ignoring case), so no entry can
    write into another's goldens.

Docs and tests

  • The README's language notes say that wrap: wrap collapses runs of
    spaces and breaks at an NBSP, and that store numbers are doubles
    (integers beyond ±2^53 lose precision; pass them as strings). The Go
    docs of Bind, Set, and Batch and the host fd comments are
    corrected.
  • New fuzz targets for the stylesheet parser and fmt idempotence, a
    property test of the ANSI frame diff, and tests of internal/play's
    step grammar. CI cross-compiles and vets for Windows (amd64, arm64).

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 28 Sep 14:27
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.3.1

Docs: https://tuimark.vercel.app

0.3b rounds out version="3"'s layout vocabulary and adds two ways to
drive a document from outside Go: tuimark host, a wire protocol for a
parent process in any language, and tuimark mcp, a Model Context
Protocol server for coding agents. Everything is additive: version="1"
and version="2" documents keep their meaning and their dumps.

Added

  • Layout and CSS (version="3"). row-gap and column-gap (0-4)
    split gap into its two axes — column siblings, grid rows/columns, and
    tab/hint strips on one, stacked siblings on the other — while gap
    stays their shorthand in the cascade, so a document that sets only gap
    lays out as before. wrap: truncate-start and wrap: truncate-middle
    join truncate, keeping the end (…efgh) or both ends
    (/Syste…/Data) of a cut line instead of the start. <column priority="N"> hides a table column, lowest priority first, once the
    visible columns no longer fit, and brings it back once they do, in
    place of @media rules for column hiding. <sparkline scale="NAME">
    shares one value range across every sparkline of a frame with the same
    name, so a group of gauges reads on the same scale.
  • Several <keymap> elements are now allowed, in every version: their
    rows form one keymap in document order. <keymap when="SEL">
    (version="3") gives every row inside it that when, unless a row
    writes its own, which replaces it outright.
  • <scroll id="log" stick="bottom"> (version="3") keeps a growing
    viewport — a log, a chat — showing its end as content arrives, and stops
    sticking while you scroll up to read back, resuming once you scroll (or
    move-last) back to the end.
  • <modal focus="#id"> (version="3") names the node that takes
    focus when the modal becomes the top one, and the fallback when the
    node focused inside it is lost (it leaves the frame or can no longer
    take focus).
  • Tab label templates. In version="3", a <tab>'s label and
    short are templates, resolved every frame like a <text> body
    ({path}, no expressions); in version="1"/version="2" a {path} in
    either stays V003.
  • tuimark host FILE [--data FILE.json] [--theme dark|light] [--reply-timeout 5s] runs a document in the terminal for a parent
    process written in any language: the terminal stays on fd 0/1/2 exactly
    as with Run(), the parent writes JSON lines to fd 3 (set, bind,
    batch, get, reply) and reads JSON lines from fd 4 (ready first,
    then event/ack/error, exit last). Each host action waits for the
    parent's reply up to --reply-timeout (default 5s), and the fd 4
    queue is bounded, so a parent that stops reading ends the session
    instead of freezing the terminal. POSIX only; a usage error on Windows.
    examples/host-ts is a Bun parent that drives it, forwarding
    SIGTERM/SIGHUP to the child and never reading the terminal itself.
  • tuimark mcp is a Model Context Protocol server on stdin/stdout
    (JSON-RPC 2.0, one message per line, standard library only) with five
    read-only tools that run the authoring loop: tuimark_validate,
    tuimark_dump, tuimark_play, tuimark_inspect, and tuimark_agents.
    Each returns exactly what the matching CLI command prints to stdout,
    byte for byte; a result is an error only when the command itself would
    exit 1 (a validation failure, exit 2, is a normal result). Register it
    with {"command": "tuimark", "args": ["mcp"]}.
  • PlayOptions.Frames makes Play also return one PlayFrame per
    applied step, in PlayResult.Frames — the same frames
    tuimark play --frames prints.

Changed

  • Batch's returned error is now every failed part joined with
    errors.Join
    , first each failed Set's own error in call order,
    then fn's own error when it differs. This is a behavior change:
    errors.Is still finds a sentinel error inside it, but comparing the
    result with == against a sentinel, or a type switch on it, no longer
    matches, since the returned value is always a joined error, even for
    one failure. Callers that compared Batch's error directly need
    errors.Is/errors.As, or Unwrap() []error to walk every part.
  • examples/monitor moves to the 0.3b vocabulary: its one <keymap>
    splits into several <keymap when="…"> groups, each row keeping its
    place in dispatch order, <hints>, and the IR keymap array; #cores
    gains row-gap: 0 so its rows touch while gap: 1 still separates the
    columns; and the Processes table's columns hide by priority — c-io
    and c-user first, then c-thr, then c-mem — in place of the
    @media rules that hid them before. Its goldens and
    monitor_resize.yml are updated to match, and scale and modal
    focus= are deliberately left unused there.
  • The V002/V003 diagnostics for a 0.3b tag, attribute, or value
    (stick, focus on modal, when on keymap, priority, scale,
    the two new wrap values, row-gap/column-gap, a tab's {path}
    label/short) used in a version="1" or version="2" document now
    end in (requires version="3"), matching the existing hint for
    version="2" items used in version="1".

Fixed

  • A focused viewport's keys clamp at its end. down or end at the
    end of a focused <scroll> stored an offset past it, so an up in the
    same terminal read was lost and the scroll ended one row off. Its keys
    now clamp to the viewport's maximum, as move-* and the wheel already
    did.

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 28 Sep 03:47
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.3.0

Docs: https://tuimark.vercel.app

The host API grows for real embedding and testing, and a new document
version, version="3", reports content that a view cuts without saying
so. Everything is additive: version="1" and version="2" documents
keep their meaning and their dumps.

Added

  • LoadFS(fsys, name) loads a document and its stylesheets from an
    fs.FS, so a view embedded with go:embed loads without being copied to
    disk. A <style src> that climbs out of the file system is a V006
    diagnostic.

  • Get(path) returns a copy of a store value. @focus, @screen, and
    @theme return the app's current focus, screen, and theme.

  • Batch(fn) applies several Sets as one change, all or nothing, with
    one redraw. No frame, dump, or handler sees half of it.

  • Play(opts, steps...) drives the app without a terminal, for tests.
    It uses the step grammar of tuimark play, calls the registered
    handlers, keeps the state for the next call, and returns the events and
    the final dump. One engine now serves tuimark play, tuimark test, and
    Play, and the CLI output is unchanged.

  • <tui version="3"> is a version="2" document plus two layout
    warnings:

    • L008: a text loses columns or lines without an ellipsis.
    • L009: a node cuts a child on an axis it does not scroll.

    Dumps mark the node with clipped. A cut the author intends is silenced
    by writing wrap: nowrap or overflow: hidden. tuimark ir prints IR
    "0.3" for these documents (schema/ir.v0.3.json), and
    schema/dump.v0.2.json gains clipped.

Changed

  • examples/monitor is a version="3" document. Its overview grid
    now scrolls instead of being cut at 40–58 columns.
  • The V003 message for an invalid version now lists "1", "2", or
    "3".

Fixed

  • Release. The release workflow now publishes the Homebrew cask. For
    v0.2.0 the cask upload failed on a template function GoReleaser does not
    define, and the cask was published by hand from the release's checksums.
  • examples/monitor. Enter or space no longer confirms a kill. The
    confirmation's buttons are focusable="false": focus rests on the
    modal, so only y, n, and esc act there, and a click on either
    button still works. Before, focus went to the first button, "yes (y)",
    and enter or space fired it. Views that copied the example should make
    the same change.

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 18:51
brew install --cask abdul-hamid-achik/tap/tuimark
go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@v0.2.0

Docs: https://tuimark.vercel.app

The first public release. It implements version 0.2 of the language in two
parts: runtime, terminal, and tooling improvements that apply to every
document, and a new version="2" vocabulary that documents opt into.
Existing version="1" documents keep their meaning, and their dumps are
unchanged.

Added

  • Installation. Release archives for macOS, Linux, and Windows (amd64
    and arm64), a Homebrew cask (brew install --cask abdul-hamid-achik/tap/tuimark), and go install github.com/abdul-hamid-achik/tuimark/cmd/tuimark@latest.
  • version="2" documents. The new vocabulary below is available only
    in <tui version="2">. In a version="1" document each new tag,
    attribute, value, property, pseudo-class, media feature, or built-in
    action is a diagnostic that ends in (requires version="2").
  • New widgets. <table> with <column> (header, per-row cell
    templates, column widths in cells, %, fr, or auto, and virtualized
    rows so only visible rows are laid out), <tabs>/<tab> (a tab strip
    with short labels for narrow terminals, and focus on activation),
    <hints> (a key-hint bar built from the keymap, with label and
    keycap), and <sparkline>.
  • Layout and style. layout: grid, scrollbar, bar: eighths for
    smooth progress bars, class:NAME="path" guards, the :checked and
    :focus-within pseudo-classes, theme="auto" (asks the terminal for its
    background color, then falls back to COLORFGBG, then to dark), and
    @media (theme: dark|light).
  • Interaction. each on containers, multi-select on list and
    table (a checked array), built-in actions for moving, checking, and
    switching tabs, when guards evaluated over the focus chain, and mouse
    input (SGR reports: click on release and the wheel).
  • tuimark play. Drives a document with simulated keys, text, paste,
    resizes, and mouse clicks without a terminal, and prints the resulting
    frames as text or JSON.
  • tuimark inspect. Explains one node, by id or by cell: its path,
    classes, pseudo-classes, and each style property with the rule that set
    it and the rules it overrode.
  • More dump and CLI options. dump --styles, --theme dark|light on
    dump, validate, preview, and play, preview --color, and a
    superset check in tuimark test --update so a golden only gains fields.
  • TUIMARK_LOG. Writes an NDJSON log of a Run() session (start,
    keys, pastes, mouse reports, actions, and frames) and redacts input
    while a secret input has focus.
  • Environment variables. TUIMARK_COLOR, TUIMARK_THEME, and
    TUIMARK_SYNC override color, theme, and synchronized-output detection.
  • Schemas. schema/ir.v0.2.json and schema/dump.v0.2.json; the JSON
    dump of a version="2" document lists each node's classes.
  • Examples. examples/unicode (CJK text and emoji) and
    examples/monitor, a process-monitor studio built with tables, tabs,
    hints, sparklines, the grid layout, and the mouse.
  • tuimark version now also prints the commit and build date for
    release builds.

Changed

  • Unicode. Text is measured, cut, wrapped, painted, and edited by
    grapheme cluster with built-in Unicode 15 width tables, so wide
    characters and emoji no longer break borders or alignment. The JSON dump
    marks wide cells with "wide": true.
  • Auto-fit scrolling. A list, scroll, or overflow: scroll
    container without a size on its scroll axis now fits the available space
    and scrolls (also in version="1" documents), with a new L006 warning;
    dump nodes carry their scroll state.
  • Terminal output. Run() probes the terminal once at start-up, picks
    a color profile (truecolor, 256, 16, or none) and downsamples colors to
    it, uses synchronized output when the terminal supports it, handles
    bracketed paste as a single edit, and never mistakes terminal replies or
    mouse reports for key presses.