Skip to content

Releases: VidGuiCode/assemblash

Assemblash 1.8.0

Choose a tag to compare

@VidGuiCode VidGuiCode released this 12 Sep 15:45

Assemblash 1.8.0

Clipping and cropping. A layer can mask itself to a rectangle or an ellipse, an
image layer can show one rectangle of its source, and any layer can mirror
horizontally or vertically. This is how a circle avatar or a rounded screenshot
is made, and how a crop becomes a rectangle rather than a fit mode.

schemaVersion stays 1. The Operation union does not grow: all four values
are ordinary UpdateLayer fields.

Added

  • clip. A layer masks itself to its own box: {"shape":"rect", "cornerRadius":12} or {"shape":"ellipse"}. A radius larger than the box
    makes a stadium, the same way a shape rectangle's radius does.
  • crop. An image layer shows one rectangle of its source, in source
    pixels: {"x":0,"y":0,"width":400,"height":400}. It composes with fit.
    Values outside the source clamp to it, and a rectangle that misses the source
    is refused.
  • flipHorizontal and flipVertical. A mirror about the centre of the
    box, composed with the rotation. Mirroring text mirrors its glyphs.
  • Every surface carries them. The CLI takes set --clip-rect,
    --clip-radius, --clip-ellipse, --no-clip, --crop X,Y,W,H, --no-crop,
    --flip-h and --flip-v. MCP update_layer takes clip, clearClip,
    crop, clearCrop, flipHorizontal and flipVertical. The interface has
    one inspector row per feature. HTTP needs nothing: POST /operations
    deserialises the union.
  • An imported image records its pixel size. A crop is a rectangle in the
    source's own pixels, so the header of a PNG, JPEG, GIF or WebP is read at
    import. A format this build cannot measure is imported with an unknown size,
    and a crop of it is refused by name rather than guessed at.

Fixed

  • A clip and a shadow on one layer now keep the shadow. The mask is applied
    inside the effect stack, because SVG applies a clip-path after a filter
    and would cut the shadow off at the boundary.
  • A rotated clipped layer is cut to the box its own coordinates name. The mask
    carries the inverse of the layer's rotation, so it does not turn with the
    content.

Older builds

Builds 1.0 through 1.7 open a 1.8.0 document, keep clip, crop,
flipHorizontal and flipVertical exactly as written, and render the layer
unclipped, uncropped and unflipped. That is a silent degrade, named here rather
than discovered later.

A layer with no clip, no crop and no flip renders exactly as it did in 1.7.1.
Every determinism golden from 1.7.0 is byte-identical.

Assemblash 1.7.1

Choose a tag to compare

@VidGuiCode VidGuiCode released this 12 Sep 13:06

Assemblash 1.7.1

Bugfix release. font install now delivers a bold face. fontWeight: 700
then works for install-only users. This release contains only the defect
registered against 1.7.0. No new capability. No schema change. No operation
change. No new surface. No dependency bump.

Fixed

  • A store built only through font install held no bold face. Every
    fontWeight: 700 layer then failed with a typed MissingFont.
    The
    bundled manifest shipped Noto Sans, Noto Serif and Noto Sans Mono only as
    variable fonts. The font database sees only the 400 default face of a
    variable font. The manifest now also registers a bold (700) face for each
    of the three default-pack families. Each new entry pins sha256 to a
    commit of the notofonts distribution. An install of a family now installs
    every face registered under its name. font install --list and
    GET /api/fonts/catalogue still answer once per family. The byte totals
    now include the bold faces.

Rendering does not change. The engine, the gate goldens, and the typed
refusal for a truly absent face stay the same.

v1.7.0 — typographic control and the canonical capability surface

Choose a tag to compare

@VidGuiCode VidGuiCode released this 12 Sep 03:54

v1.7.0 — typographic control and the canonical capability surface

Bold headlines, tracked captions, outlined display text, and text that sits
where the box says it should — plus one capability listing that every
surface reads, so what the engine renders is exactly what it advertises.

schemaVersion stays 1 and the Operation union is unchanged. The
oldest build that opens a 1.7.0 document is 1.0: builds 1.0 through 1.6
degrade silently through the Extras capture map, rendering the regular
face with no spacing, stroke or vertical alignment.

Added

  • fontWeight (100–900) and fontStyle on text layers, resolved by
    exact face: a weight the font store has no face for is refused with a
    typed MissingFont naming family, weight and style — never substituted.
    Set them through assemblash set/add-text (--weight, --font-style),
    MCP add_text_layer/update_layer, the HTTP create/update payloads,
    presets, and the interface.
  • letterSpacing (px) on text layers, part of measurement itself —
    wrapping, /text-layout, the overflow warnings and the export all agree.
  • stroke on text layers, painted centred on the glyph outline with
    paint-order="stroke". A layer with color: null and a stroke is hollow
    display text.
  • verticalAlign (top | middle | bottom, default top), so
    existing documents render pixel-identically.
  • The canonical capability listing. assemblash styles --json, HTTP
    GET /api/capabilities, and the new MCP list_capabilities tool serve
    one identical listing of every blend mode and effect this build renders,
    with examples and notes. Check it before building.
  • Four new cross-target determinism gates for the new paint paths
    (text-weight, text-stroke, text-spacing, text-valign); every
    pre-existing golden is byte-identical.

Upgrade: 1.7.0 is a drop-in replacement for 1.6.x. Documents written by
1.7.0 open unchanged in 1.6.1 and in every build back to 1.0 (new fields are
preserved, just not rendered, by older builds).

v1.6.1 — styles tells the truth

Choose a tag to compare

@VidGuiCode VidGuiCode released this 11 Sep 22:31

v1.6.1 — styles tells the truth

A defect sweep on 1.6.0. No schema change (schemaVersion stays 1), no
operation-shape change, no new surface.

Fixed

  • assemblash styles now lists dropShadow. 1.6.0 rendered the effect
    but the CLI's discovery listing still named only the five pre-1.6 effects,
    so an agent reading styles concluded drop shadows were unsupported. The
    listing is now derived from the Effect enum in core instead of a literal
    array in the CLI, a new effect variant fails to compile until it is given
    its line, and the regression test asserts every variant by an exhaustive
    match. The dropShadow entry documents its four fields (dx, dy,
    blur, color), notes that dx/dy at 0 is a glow, and that an
    #rrggbbaa alpha sets flood opacity.
  • The add-text --text and set --text help no longer claim `\n`
    is a line break.
    A backslash-n typed at the shell is stored as the two
    characters and renders as such; only --text-file produces a real line
    break. The help now says the text is taken verbatim and points at
    --text-file for a line break. Behaviour is unchanged.

Upgrade: 1.6.1 is a drop-in replacement for 1.6.0. Documents written by
1.6.1 open unchanged in 1.6.0 and in every build back to 1.0.

v1.6.0 — shapes and shadows

Choose a tag to compare

@VidGuiCode VidGuiCode released this 09 Sep 20:27

No 1.5.1 was released: no defect was outstanding after 1.5.0.

A rectangle, an ellipse and a line stop being things you draw elsewhere and import: they are layers the document draws itself, with fill, stroke and corner radius as ordinary properties. A drop shadow joins the effect stack, and a glow is that shadow with no offset.

Added

  • Shape layers, a fifth layer kind: rect (with cornerRadius), ellipse and line. The transform box is the geometry, so move, resize and rotate mean what they already mean for every other layer. A line is its box: width is the length, rotation the angle.
  • fill and stroke ({ color, width }), either absent for no paint. Strokes are painted inward on a rect and an ellipse, so the box stays the visual box; a line's stroke is centred. Widths below 1 draw as hairlines and may spill up to half a pixel.
  • A dropShadow effect with dx, dy, blur and color; an #rrggbbaa alpha is its opacity. Zero offset is a glow.
  • Presets carry fill and stroke, and a colour slot may target a shape's fill as well as a text layer's colour.
  • CLI add-rect, add-ellipse, add-line, and --fill, --stroke, --stroke-width, --corner-radius on set (none clears a paint).
  • MCP add_shape_layer, taking the server from 44 tools to 45; update_layer gains fill, stroke, cornerRadius, clearFill and clearStroke; layer summaries report a shape's geometry.
  • Interface: a Shapes row in the Add panel, a Shape inspector (Fill, Stroke, Stroke width, Corner radius), and dropShadow in the effects editor.
  • Two new reference documents in the render gate, shapes and shadow, checked byte for byte on all six release targets.
  • A new README hero, built with this release as the editable example project examples/readme-hero.

Changed

  • Asset uploads accept up to 64 MiB (previously the framework's 2 MB default). A body over the limit on either upload route now answers with the JSON error envelope: status 413, code payloadTooLarge.

Fixed

  • A font family literally named catalogue or install can now be removed; DELETE sits on both fixed paths.

Compatibility

  • schemaVersion stays 1; the operation union is additive.
  • 1.0 through 1.5 refuse a document that contains a shape layer — the whole document — with unknown variant "shape", expected one of "text", "image", "group", "svg". Nothing needs migrating: open the project with 1.6.0 or newer. A dropShadow effect is preserved by every 1.3.0-and-newer build and only its render is refused.
  • Rounded corners and ellipses are emitted as cubic curves rather than SVG arcs, keeping the rasteriser's arc conversion and the platform maths library off the path to a pixel; shadows get an explicit filter region.

Downloads below are built by CI for Windows, Linux and macOS on x86_64 and aarch64; SHA256SUMS covers the archives.

v1.5.0 — the no-terminal promise, kept

Choose a tag to compare

@VidGuiCode VidGuiCode released this 09 Sep 03:47

Manage fonts from the editor instead of a terminal: list the installed families, import TTF, OTF, TTC, OTC, WOFF and WOFF2 files, remove a family after a confirmation, and, when the store is empty, install the bundled default pack (Noto Sans, Noto Serif, Noto Sans Mono) with one button that says what it will download. The same store is reachable over HTTP under the usual access-token rule; the install route is the one route that reaches the network, only on that request, and a pack install is atomic.

A server started by the double-click launch (or serve --reclaim-stale-locks, also on mcp) reclaims a project lock by itself only when the lock names this machine and the process it names is provably gone. Every reclaim is reported in the server log, in the editor, and as a lockReclaimed export warning. A lock from another machine, or from an older build that recorded no machine, still needs a person.

An imported SVG asset whose text no loaded font can draw is now refused with a typed error naming the asset, instead of exporting blank and calling it a success. A document that used to export blank but successfully now refuses: name a loaded family in the asset or outline the text before importing it. No 1.4.1 was released: no defect was outstanding after 1.4.0.

The document schema remains version 1 and every existing operation shape is unchanged. 1.4.0 and earlier open every project 1.5.0 writes; the only new on-disk data is the optional host in a lock file, which older builds ignore.

Downloads cover Windows, Linux and macOS on x86_64 and ARM64. Archives include license notices; SHA256SUMS lists checksums. Debian packages and standalone Windows executables are also provided.

v1.4.0 — Canvas editing

Choose a tag to compare

@VidGuiCode VidGuiCode released this 05 Sep 14:34

Resize an existing canvas, change its background, and place content using nine anchors. Layers retain their sizes. Use the Canvas editor panel, assemblash canvas set, HTTP updateCanvas, or MCP update_canvas.

Every change uses the existing validated operation layer: protected content is respected, MCP supports dry runs and expected versions, and one undo restores dimensions, positions and background together. Unknown operation and HTTP request fields now produce explicit errors; actor.detail remains accepted as an alias for name.

The document schema remains version 1 and existing operation shapes are unchanged. After a project records updateCanvas, use 1.4.0 or newer. The 1.3.1 binary refuses both show and history because it does not recognize the new journal operation, including after undo. Preserve the journal and keep the newer binary.

Downloads cover Windows, Linux and macOS on x86_64 and ARM64. Archives include license notices; SHA256SUMS lists checksums. Debian packages and standalone Windows executables are also provided.

v1.3.1 — Reliable release checks

Choose a tag to compare

@VidGuiCode VidGuiCode released this 05 Sep 13:19

This patch makes the test and release checks more reliable without changing the document schema, operation API or editor features.

  • MCP integration tests build the executable they exercise and use Cargo's reported artifact path.
  • HTTP test clients stop at a complete response instead of waiting for the socket to close.
  • Browser test startup, commands and cleanup have bounded waits; CI and release jobs have time limits.
  • Cross-transport smoke tests run on all six targets before release and against checksum-verified downloaded archives after publication.

schemaVersion remains 1. Existing documents retain the same rendering pixels; exported metadata records renderer version 1.3.1.

v1.3.0 — Every capability reachable from every surface

Choose a tag to compare

@VidGuiCode VidGuiCode released this 04 Sep 07:34

Nothing new that the engine can draw; everything it could already draw becomes
reachable from every interface. A layer property only the HTTP API could set is
now a CLI flag. A query only the CLI could answer is now an HTTP route and an
MCP tool. An export now says what it could not do well instead of staying
quiet. And an operation carrying a property it does not define is refused,
where it used to be accepted and silently discarded.

Added

  • assemblash set, one command for every updatable layer property:
    --name, --x, --y, --width, --height, --rotation, --opacity,
    --visible, --locked, --blend, --effects, --effects-file, --text,
    --text-file, --font, --size, --color, --align, --line-height,
    --fit, --asset and --allow-locked. However many flags an invocation
    carries, it is one update operation: journalled once, undone once. Position
    and size flags are merged into the layer's current transform, so --x alone
    moves without resizing, and --name "" removes a name. assemblash style is
    unchanged and now calls the same builder, so the two cannot drift.
  • --line-height on assemblash add-text, and lineHeight on the MCP
    update_layer tool
    — the last updatable property those two surfaces could
    not set.
  • Export warnings. An export now reports what it could not do well:
    wordBrokenMidWord (a single word too wide for its box, split to fit),
    textOverflowsBox (laid-out text taller than the box holding it) and
    svgAssetTextWithoutFont (an imported SVG asset drawing text in a family no
    loaded font provides). Each carries code, message and, where one applies,
    layerId. POST /api/projects/{id}/export and the MCP export_document
    result carry a warnings array; the CLI prints one line per warning on
    stderr, or the whole array as JSON on stdout with the new --warnings-json
    flag on render and export. A warning is advisory: it changes no pixel and
    no exit status.
  • GET /api/projects/{id}/overlaps, returning {"pairs": …} in the same
    order as assemblash overlaps, narrowable with a repeated or
    comma-separated ?layers=. A layer id that is not in the document is
    refused, not ignored.
  • Four MCP tools — create_project, add_svg_layer, render_document and
    find_overlaps
    — taking the server from 39 tools to 43. render_document
    returns the same SVG the HTTP preview route serves; find_overlaps returns
    the same pairs as the CLI and the new route; add_svg_layer draws an asset
    that was already imported, because a layer can only ever draw a sanitized
    one.
  • Positional output paths on assemblash render and assemblash export,
    beside the existing --out, and both now print the written path and its
    sha256: digest as one tab-separated line on stdout — the same digest form
    variants already prints — so two interfaces can be compared without
    hashing the files.
  • Positional input files on assemblash add-svg and assemblash add-image, beside the existing --file, matching assemblash font add.
  • assemblash preset define --properties-file, so a preset's JSON can come
    from a file rather than from a shell that may rewrite it on the way through.
  • An SVG choice in the reference editor's export dialog. Choosing SVG
    downloads the engine's vector render and disables the resolution row, since a
    scale means nothing for a vector; nothing is written into the project.
  • Reference-editor inspector controls for image and SVG fit, and for
    reordering the effect stack, where order is part of what the stack means. The
    font fields gained a suggestion list of the families the server reports, with
    free text still accepted so a document naming a family this machine lacks
    stays editable. An uploaded image or SVG now arrives at its own recorded
    dimensions, scaled down to fit the canvas, instead of always 300×200.

Changed

  • The reference editor's export dialog no longer carries its bare SVG download
    link. The link could not carry an access token, so it failed on any server
    started with one, and the new format choice is the working version of the
    same affordance.
  • CI's ui job now runs npm run check, which typechecks, builds and runs the
    interface's unit tests and browser journeys in one step. Those tests existed
    but had never been invoked in CI. The check that the committed build output
    is the build output still runs after it, unchanged.

Fixed

  • An operation carrying a property it does not define used to succeed.
    {"op":"update","id":"layer_…","letterSpacing":4} returned 200 OK, bumped
    the document version, reported the layer as changed, and journalled an update
    carrying no properties at all — a false success that told a client its edit
    had landed when nothing had happened. create behaved the same way. Both now
    return 422 with code operationRefused, naming the property; the document
    version does not move and no journal entry is written. The known keys are
    derived from the same definitions the published operation schema is generated
    from, and the create check uses the payload's own type, so a text
    property on an image create is refused as precisely as a misspelt one. Three
    spellings that predate 0.6.0 — font_family, font_size and line_height
    on a text create — are still accepted, so journals written before then
    still replay.

    This affects only a newer client talking to this server. It is a refusal of
    what is sent, never of what is stored: no document, journal or existing
    client behaviour changes meaning. MCP is unaffected — every MCP tool
    builds a typed operation in Rust, so a property the operation does not define
    could never reach the operation layer from there.

Compatibility and safety

  • schemaVersion stays 1.
  • The existing Operation union is unchanged, and no operation changed shape.
    set and all four new MCP tools compile to variants that already existed.
    schema/document.schema.json, schema/operation.schema.json and both
    generated TypeScript declaration files are unchanged.
  • A document written by 1.2.x is unchanged by this release, and every 1.x
    build — back to 1.0.0 — reads a document produced with this release's
    capabilities exactly as it read one before it. No document field was added
    and none changed shape.
  • warnings is a new field on an export response, not on a document, so it is
    additive for any client that ignores unknown response fields.
  • The published operation schema does not state whether extra keys are allowed
    on create and update. The refusal lives in the operation layer, so a
    client author should read this behaviour rather than the schema's silence.
  • Export warnings are not displayed in the reference editor in this release.
    They are reported by the CLI, the HTTP API and the MCP server.
  • svgAssetTextWithoutFont makes a silent failure loud; it does not fix it. An
    imported SVG asset drawing text in a family the loaded fonts do not provide
    still renders that text as nothing. The check reads font-family attributes
    only — not a style="…" attribute and not a <style> block.
  • Every renderer gate golden is unmoved: this release adds no paint path.
  • Every existing CLI flag and subcommand, HTTP route and MCP tool still works.
    --out, --file and assemblash style are all still accepted.

v1.2.1 — Nothing quietly dropped

Choose a tag to compare

@VidGuiCode VidGuiCode released this 04 Sep 00:12

A bugfix release on 1.2.0. Five defects found by auditing the released build
against what it claims to do, four of them cases where something was quietly
discarded or quietly duplicated rather than reported. Nothing new was added,
and this is the 1.2 release to install.

Fixed

  • A font that names its family in more than one language arrived as more than
    one family.
    assemblash font add wrote one index record per name-table
    language record, so a font naming itself in English and Japanese filled
    font list with families nobody had installed. One file's face is now one
    record, under the name the English record gives, or the first record when
    there is none.
  • The same font bytes under a different filename were added twice. A stored
    record compared equal only when its source and license matched too, so
    re-adding a renamed copy — or the same file again with --license filled in
    — appended a second row describing a file already in the store. Records are
    now matched on what they identify: family, style, weight, file, hash and face
    index. The first import's source and licence are kept.
  • SVG import destroyed the accessibility metadata an author had written.
    role, aria-label, aria-labelledby and aria-describedby were not on the
    import allowlist and were stripped without appearing in the removal report.
    They are now kept. None of them references anything outside the document, so
    nothing about the import threat model changes.
  • prune_unused_assets treated an svg layer's asset as an orphan and
    would have deleted a file the document was still drawing. It now counts every
    layer kind that names an asset, and does so exhaustively, so a layer kind
    added later cannot be forgotten here silently.
  • The published schema described blendMode wrongly. Its description still
    read "Reserved (v0.5): only normal is rendered today", fourteen releases
    after fourteen modes started rendering, and that sentence was in
    schema/document.schema.json and schema/document.d.ts as well as the Rust.
    It now says what the renderer does, including why color-dodge and
    color-burn are deliberately absent and what happens to a mode this build
    does not draw.

Changed

  • Removed drawInspectorLegacy from the reference interface. It was superseded
    and called from nowhere; the committed bundle is smaller by its size.
  • crates/assemblash-renderer/tests/fonts/TwoFamilyNames-Subset.ttf, a Noto
    Sans subset carrying two Unicode family-name records, is committed as a test
    fixture for the first of the font-store fixes. Like the other fixtures there
    it is under the SIL Open Font License 1.1; it is renamed rather than keeping
    the original family name, because it is a modified version. subset.py
    rebuilds it from the committed Latin subset.

Compatibility and safety

  • schemaVersion stays 1.
  • The existing Operation union is unchanged, and no operation changed shape.
  • No CLI flag or subcommand, HTTP route, MCP tool or interface control was
    added, changed or removed.
  • A document written by 1.2.0 is unchanged by this release: it loads, saves,
    renders and exports exactly as it did, byte for byte. The only change to a
    published artefact is a corrected description string in the generated schema
    and TypeScript declarations, which no document depends on.
  • Every renderer gate golden is unmoved. No dependency was bumped.
  • Documents whose SVG assets were imported by an earlier build are unaffected;
    the accessibility fix applies to imports made from now on, and re-importing
    the original file recovers what an earlier import dropped.

Install

Download the archive, the bare executable, or the .deb for your target from
the assets below, or install from source with Rust 1.92 or newer:

cargo install --git https://github.com/VidGuiCode/assemblash --tag v1.2.1 assemblash-cli

SHA256SUMS in the assets lists the checksum of every file attached here.

See the changelog
for the complete release notes.