scrin 0.2.1

A terminal UI toolkit with panes, widgets, overlays, animations, and Aisling-powered effects/loaders.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# Scrin 0.2.1 — MP4 examples and recording workflow

Date: 2026-09-29. Repository: KnottTUI. Crate: `scrin`.

## What changed

This release adds a repeatable video workflow and actual MP4 examples of Scrin
widgets composed with Aisling effects and loaders. Library APIs and dependency
requirements are unchanged from 0.2.0. Applications can adopt `scrin = "0.2.1"`;
apps still on 0.1 should follow the 0.2 migration notes below.

The user's ongoing requirement is recorded in `AGENTS.md` and the README release
instructions: make good MP4 examples for releases, including Aisling alongside
the features being demonstrated. Regenerate videos, inspect representative
frames, and validate them before publishing.

## Videos and gallery

`docs/previews/index.html` is a local gallery with manual playback controls,
posters, and direct MP4 downloads. The collection includes:

- `workspace.mp4`: 12 seconds of a native file list, Rust code, scripted input,
  progress gauge, Aurora/Hologram/Prism text, and an Orbit loader. The code sample
  changes effect kind with the preview.
- `effects.mp4`: 12 seconds showing the nine Aisling 0.4 effects together in Scrin
  panes. Each has deterministic seeded playback and a final hold.
- `loaders.mp4`: 12 seconds showing Tqdm, Orbit, Matrix, Fireworks, GridPulse, and
  Bar beside a task queue, sparkline, and Hologram text. Progress is demo data.
- `showreel.mp4`: the three scenes in sequence, totaling 36 seconds.

The clips are silent, 1920x1080, 30 fps, H.264/yuv420p, with MP4 faststart for
progressive playback. Source frames come from real Scrin buffers; the videos
are deterministic exports, not terminal performance measurements. `manifest.json`
records the producing crate version, dimensions, frame counts, and durations.

## Reproduce and validate

```bash
scripts/video_previews
scripts/check_video_previews

# Optional focused/short export.
SCRIN_VIDEO_OUTPUT=/tmp/scrin-preview scripts/video_previews --scene workspace --frames 90
scripts/check_video_previews /tmp/scrin-preview
```

The optional recording tools require Python 3 with Pycairo and PyGObject/Pango,
FFmpeg with libx264, FFprobe for validation, and Rust. Preferred fonts are Adwaita
Mono and Adwaita Sans, with Pango fallback. No desktop, capture service, network
service, or new Rust dependencies are needed. Existing Cargo environment settings,
including `CARGO_TARGET_DIR`, are honored.

`--scene all|workspace|effects|loaders` selects scenes. `--frames N` accepts 1–3600
frames per clip, default 360. The wrapper checks optional dependencies and uses a
pipe with error propagation; no raw frame files are retained. Failed recording or
encoding leaves the previous videos intact. A filtered run's manifest/gallery
lists only that run's clips; release recordings should use the default full set.

The cell export includes RGB foreground/background, bold/italic/underline flags,
and display widths/continuation cells. The renderer paints backgrounds before
wide glyphs so a continuation background cannot erase half a character.

## Changed files

- `examples/export_video_frames.rs`: native widget/effect scenes, deterministic
  animation/progress, configurable frame counts, and JSON-lines buffer export.
- `scripts/video_previews`: recording entry point and optional tool checks.
- `scripts/render_video_previews.py`: Pango/Cairo cell rasterization, staged FFmpeg
  encoding, posters, concatenated showreel, manifest, and accessible local gallery.
- `scripts/check_video_previews`: FFprobe codec/dimension/frame/duration checks and
  FFmpeg decoded motion samples.
- `docs/previews/**`: generated MP4s, posters, gallery, manifest, and recording docs.
- `AGENTS.md`: durable project/release instructions, including the MP4 requirement.
- `Cargo.toml`, `Cargo.lock`, `src/lib.rs`, `README.md`, `for_code_tool.MD`,
  `changes.MD`, `newest_release.MD`, `new_changes.md`: version 0.2.1, documentation,
  release notes, and editor handoff. Prior handoffs remain below.

Generated media and `AGENTS.md` remain outside the Cargo package. The exporter and
recording scripts are included so crate users can reproduce the examples. No
incoming KnottCode requests or sibling repository sources are changed by this work.
Root-document include patterns in `Cargo.toml` are anchored with `/` so the
gallery's nested README does not accidentally enter the Cargo package.

## Verification

- All four complete MP4s pass `scripts/check_video_previews`: H.264/yuv420p,
  1920x1080, 30 fps, 360 frames per scene and 1080 frames in the showreel, expected
  durations, successful decoding, and varying motion samples.
- Visually reviewed posters and nine early/middle/final keyframes extracted from
  the encoded videos. Pane text, code, animation, progress, and final text remain
  legible; the final export uses a consistent dark background and subdued input.
- Short 90-frame exports pass the same video validator. Invalid scene names and
  out-of-range frame counts are rejected before emitting frame data.
- A deliberately truncated frame stream fails safely, preserves a previous
  recording, and removes its temporary output. Gallery media/poster links resolve.
- `cargo test --offline --locked --quiet`: 314 unit/integration tests and 3 doc
  tests pass. `cargo check --offline --locked --examples`: all 12 examples compile.
- Strict rustdoc (`RUSTDOCFLAGS=-Dwarnings`), Rust formatting, diff whitespace,
  Bash syntax, and Python compilation checks pass.
- `cargo publish --dry-run --locked --allow-dirty` passes. The 90-file archive
  includes the exporter/scripts and excludes `docs/**`, generated media, build
  artifacts, `AGENTS.md`, `for_code_tool.MD`, and `FromKnottCode.MD`.

---

# Scrin 0.2.0 — Aisling 0.4 editor handoff

Date: 2026-09-29. Repository: KnottTUI. Crate: `scrin`.

## Adopt this release

```toml
[dependencies]
scrin = "0.2.0"
```

Scrin now uses the published `aisling = "0.4.0"` dependency from KnottEffects.
The 0.2 version boundary is intentional: Scrin publicly re-exports Aisling's
`EffectKind`, whose nine new variants require exhaustive matches to be updated.
Existing `scrin = "0.1.85"` applications will not silently receive that change.
If an app also uses Aisling directly and passes its types to Scrin, change its
Aisling dependency to `aisling = "0.4"` as well.

## Added effects and configuration

All nine additions are available through `scrin::effects::EffectKind`:

| Variant | Parsed name | Animation |
| --- | --- | --- |
| `Aurora` | `aurora` | Curtains of light reveal the text. |
| `Constellation` | `constellation` | Connected stars assemble the glyphs. |
| `Hologram` | `hologram` | A flickering projection stabilizes. |
| `Ripple` | `ripple` | Concentric waves refract the text. |
| `Shatter` | `shatter` | Fragments assemble into the text. |
| `Weave` | `weave` | Interlacing threads reveal the text. |
| `Circuit` | `circuit` | Pulses power circuit traces into glyphs. |
| `Prism` | `prism` | Refracted text planes converge. |
| `Origami` | `origami` | Hinged strips unfold into the text. |

`EffectPlayer::all_kinds()` now returns 46 effects. The existing name parser,
`EffectPlayer`, `RetainedEffectWidget`, and `CachedEffectPane` accept every new
kind without a separate rendering path. Gallery demos that enumerate the catalog
pick them up automatically. `examples/demo_native.rs` now uses Aurora; run
`scripts/demo_native` to preview it with native partial redraws and input.

`ExistingColorHandling` is newly re-exported from `scrin::effects` so ANSI input
color preservation can be configured without a separate direct dependency.
The existing color, gradient, selector, and configuration re-exports remain.

```rust
use scrin::effects::{
    EffectConfig, EffectKind, EffectPlayer, ExistingColorHandling, RetainedEffectWidget,
};

let config = EffectConfig {
    existing_color_handling: ExistingColorHandling::Always,
    hold_final_frames: 8,
    ..EffectConfig::default()
        .with_canvas_size(64, 8)
        .with_duration(32)
        .with_seed(7)
};
let effect = RetainedEffectWidget::from_player(EffectPlayer::with_config(
    EffectKind::Aurora, "KnottCode", config,
));
```

## Behavior and compatibility

- Aisling 0.4 fixes `EffectConfig::no_color` so configured canvas backgrounds
  and temporary glyphs cannot restore ANSI colors. `get_ansi_string` inherits
  this fix. Scrin buffer mapping still uses the player's accent for uncolored
  foreground cells; this release does not change that existing behavior.
- Existing effect variants retain their upstream IDs and seed behavior. Player
  builders, cached rendering, frame selection, partial terminal presentation,
  interaction APIs, and the 0.1.85 fixes documented below remain available.
- No new loaders were added upstream in this release; loader APIs remain.
- No compiled/indexed Aisling renderer was added upstream. `EffectPlayer` still
  generates and stores its frame sequence once; it does not claim streaming or
  lower cold-start memory. The existing KnottCode upstream request remains
  relevant, and no duplicate request was added to `../KnottEffects/FromKnottTUI.MD`.
- No KnottCode source or manifest is modified by this release. The app must opt
  into `scrin = "0.2.0"` to receive the new effects.

## Changed files and verification

- `Cargo.toml`, `Cargo.lock`: Scrin 0.2.0 and Aisling 0.4.0; other dependency
  requirements remain unchanged.
- `src/effects/mod.rs`: re-export `ExistingColorHandling`.
- `src/lib.rs`, `README.md`: current install version, new catalog, migration
  instructions, and Aurora example.
- `examples/demo_native.rs`: native Aurora demonstration.
- `tests/aisling_04.rs`: discoverability and parsing of every new kind, raw frame
  parity with Aisling, single-frame durations, final holds, loop wraparound,
  retained cache/clipping parity, and raw monochrome checks across the catalog.
- `tests/core_tests.rs`: inventory expectation raised to at least 46 effects.
- `changes.MD`, `newest_release.MD`, `for_code_tool.MD`, `new_changes.md`: release
  notes and editor handoff. `for_code_tool.MD` stays outside the Cargo package.

Validation completed before publication:

- `cargo test --locked`: 314 unit/integration tests and 3 documentation tests pass.
- `cargo fmt --all -- --check` and `git diff --check`: pass.
- `cargo check --offline --locked --examples`: all 11 examples compile.
- `RUSTDOCFLAGS=-Dwarnings cargo doc --offline --locked --no-deps`: passes.
- `scripts/check_terminal_pty`: Aurora playback, paste, partial redraws, resize,
  Ctrl+Q, and restoration pass at 80x24 and 32x10, including tiny resizes; failed
  initialization restores raw mode too.
- A fresh temporary snapshot of `../KnottCode` compiles with its Scrin dependency
  changed to 0.2.0 and patched to this checkout. Its actual source and manifest
  remain untouched; no KnottCode source changes were needed for the upgrade.
- `cargo publish --dry-run --locked --allow-dirty`: package verification passes.
  The archive includes this handoff and the new integration tests, and excludes
  `for_code_tool.MD`, `FromKnottCode.MD`, and build artifacts.

---

# Scrin 0.1.85 — code editor handoff

Date: 2026-09-29. Repository: KnottTUI. Crate: `scrin`.

This release improves retained animation allocation, terminal rendering correctness,
partial interaction updates, and forms while retaining Scrin's native buffer model,
its Aisling integration, existing widgets, and existing public rendering APIs.
No dependency requirements were added, removed, or updated. `Cell` remains a
copyable single-character cell, and `TextInput::cursor` remains a character index.

## 1. KnottCode requests

Both requests in `FromKnottCode.MD` are implemented in this release.

### Reuse retained frame buffers

Files: `src/effects/retained.rs`, `src/core/buffer.rs`.

- `RetainedEffectWidget::with_cached_buffer` and `with_cached_frame_buffer` now
  reuse the same Scrin buffer when the frame changes without a dimension change.
- A changed frame resets all cells, colors, style flags, background, and wide-glyph
  continuation metadata before drawing. Content outside a smaller Aisling frame
  cannot retain old glyphs or styles.
- Same-frame hits still skip rendering. Resize requests rebuild storage as needed
  and continue to clip to the requested dimensions.
- `RetainedLoaderWidget::with_cached_buffer` likewise reuses its Scrin buffer when
  ticks or progress change at the same size. Aisling still generates its own loader
  frame; this does not claim that uncached loader rendering has zero allocations.
- `CachedEffectPane` compares cache keys by reference instead of cloning its text
  and palette on every lookup or rendered frame.
- Existing `render_cached_only`, `render_frame_into`, frame-count, cache-key,
  cached-row, visible-row, and area-presentation APIs remain available.

### Generate a fully configured effect once

Files: `src/effects/aisling_bridge.rs`, `src/effects/mod.rs`,
`src/effects/retained.rs`.

New constructor:

```rust
EffectPlayer::with_config_and_palette(
    kind: EffectKind,
    text: &str,
    config: EffectConfig,
    colors: Vec<scrin::Color>,
    angle: f32,
) -> EffectPlayer
```

It applies the complete configuration and palette before generating frames.
The existing `with_size`, `with_duration`, `with_seed`, and
`with_gradient_colors` builders remain compatible and eager.

New re-exports from `scrin::effects`:

- `AislingColor`
- `AislingGradient`
- `GradientDirection`

These are the Aisling types accepted by `EffectConfig::with_gradient`, for callers
that prefer to assemble the entire configuration themselves.

Recommended KnottCode construction:

```rust
use scrin::Color;
use scrin::effects::{EffectConfig, EffectKind, EffectPlayer, RetainedEffectWidget};

let config = EffectConfig::default()
    .with_canvas_size(64, 8)
    .with_seed(7)
    .with_duration(32);
let player = EffectPlayer::with_config_and_palette(
    EffectKind::Matrix,
    "KnottCode",
    config,
    vec![Color::CYAN, Color::BLUE],
    45.0,
);
let retained = RetainedEffectWidget::from_player(player);
```

`CachedEffectPane` uses this construction path internally. Palette angle mapping
is unchanged: 0 means horizontal, 90 vertical, and other angles diagonal. Effect
reconfiguration now normalizes the current frame index if the new sequence is
shorter. `advance_n(usize::MAX)` no longer overflows.

No Aisling source change is needed for these fixes, so no request was added to
`../KnottEffects/FromKnottTUI.MD`.

## 2. Buffer text, wide glyphs, and allocation

File: `src/core/buffer.rs`.

New APIs:

```rust
buffer.set_str_clipped(x, y, text, max_width, fg, bg) -> usize
buffer.set_str_styled_clipped(x, y, text, max_width, style) -> usize
buffer.reset()
buffer.repair_wide_cells()
```

- Clipped writers return terminal columns actually written, respecting both the
  supplied width and buffer bounds. They stop before a glyph that will not fit;
  they do not skip that glyph to render later characters.
- Existing plain, bold, and styled text writers now advance by display columns.
  For example, `A中B` occupies four columns without overwriting `中`.
- String writers replace control characters with spaces and skip zero-width
  formatting characters. Direct single-cell writes normalize non-displayable
  characters or a wide character in the final column to a styled space.
- Fast ASCII writes inspect the boundaries of the affected row, not every cell
  in the buffer. They can safely overwrite wide glyphs and work when another
  row contains wide text. Non-ASCII input still returns `false` from the explicit
  ASCII fast-path method without writing.
- Overwriting either half of a wide glyph clears the other half. Overlapping
  wide glyphs and fast fills no longer leave stale continuation flags.
- Wide-character fills tile pairs of columns and pad an odd final column with
  a styled space. Fills clip their loops to the buffer dimensions.
- `merge_from` skips source continuation cells so a background-colored wide
  glyph is not erased by its own second cell.
- Offset copies/merges reject out-of-bounds work without overflowing coordinates.
  Resize repairs glyphs clipped at the new right edge and does nothing if the
  dimensions have not changed.
- `reset` restores fresh-buffer cells, styles, background, and continuation state
  without reallocating. `Buffer::clone_from` reuses destination vector storage.
- `copy_area_from` expands horizontal edges to include complete intersecting
  glyphs in both source and destination and uses row-slice copies.

### Direct cell mutation compatibility

`cell_mut` and `cells_mut` remain available with the same return types. Changing
only colors/styles requires no migration. If an app changes symbols through these
APIs, call `repair_wide_cells()` afterward, or use `set` to maintain metadata
immediately. KnottCode's style-only post-render effects can stay as they are.

`Cell::ch` still stores one `char`. This release does not add grapheme-cluster or
emoji-sequence storage, nor a complete new Unicode width database.

## 3. Terminal diff, interaction metadata, and lifecycle

Files: `src/terminal/mod.rs`, `src/interaction/mod.rs`.

- Partial diffs and partial front-buffer commits use the same wide-glyph boundary
  expansion. An area covering only a continuation cell now presents the glyph
  from its leading column. Adjacent old/new wide glyphs are included as needed.
- Area-limited diff iteration visits relevant row spans rather than checking
  every terminal cell against every area. Overlapping areas do not duplicate cells.
- An unchanged or empty diff writes **zero bytes**, including no trailing SGR reset.
- Nonempty diff/full output establishes its initial style explicitly, including
  black foregrounds, so it does not inherit unknown terminal colors or modifiers.
- Full writers replace glyphs clipped by an explicitly narrower output width with
  a space. Non-displayable characters inserted through raw cell access are not
  emitted as terminal control commands.
- Terminal clears and front/back commits reuse buffer allocations through
  `reset` and `clone_from`.
- `Areas`, `MarkedDirty`, and cursor-preserving area draws retain mouse/selection/
  scroll metadata belonging to unaffected panes. New metadata is committed only
  after the presentation succeeds.
- New `InteractionLayer::replace_areas(&[Rect], replacement)` supports the same
  partial update semantics in custom render loops. Re-register all affected
  regions/spans: records intersecting a redraw are replaced as a whole, records
  outside the redraw are preserved, and replacement records outside it are ignored.
  A moved stable ID replaces its previous record.
- `Terminal::set_cursor` records the logical cursor as well as moving it, allowing
  subsequent cursor-preserving area presentations to restore that position.
- Initialization queries dimensions before changing modes and holds a cleanup
  guard while enabling output modes. An initialization output failure restores
  raw mode and attempts to undo enabled terminal modes.
- `restore` attempts paste, mouse, cursor, alternate-screen, and raw-mode cleanup
  even after an earlier operation fails. Failed output resets remain retryable;
  a successful restoration is still idempotent.

### Behavior downstream code should account for

A selected area may now update neighboring columns belonging to the same wide
glyph. This keeps the real terminal and committed buffer coherent. Partial draw
closures should register interaction metadata for the widgets they redraw.
ANSI snapshot expectations must allow explicit initial style resets and an empty
output for a clean diff. Existing method signatures and presentation strategies
remain intact.

## 4. Input fields, forms, geometry, and sanitization

Files: `src/widgets/form.rs`, `src/input/mod.rs`, `src/core/rect.rs`,
`src/scroll_state.rs`, `src/sanitize.rs`.

- Long focused text fields scroll horizontally to keep their cursor visible.
  Wide labels, wide values, masked values, and placeholders use display columns.
- New `TextInput::cursor_position(area) -> Option<(u16, u16)>` exposes the rendered
  cursor position for `Frame::set_cursor_position`. It returns `None` when the
  field is unfocused or there is no visible input space.
- Paste is filtered and inserted in one operation instead of inserting each
  character individually. Controls, including newline/tab/escape, are discarded
  in the single-line input. Alt/control character shortcuts are not inserted.
- Text editing clamps the public cursor after an application changes the value.
  Dropdown and form navigation avoid overflow on externally changed indices.
- Forms render borrowed fields without cloning their values/options, clip titles,
  dropdowns, toggles, and status text to their area, and reserve the status row.
  Input/dropdown rendering preserves configured style flags.
- Text inputs, dropdowns, toggles, and `EventRouter` ignore key-release events;
  key presses and repeats continue to work.
- `Rect::rows`, `cols`, and `positions` iterate without allocating. Empty rectangles
  do not intersect; oversized inner margins produce an empty rectangle within the
  parent's bounds. The iterator lifetime spelling no longer requires precise
  `use<...>` captures introduced after the declared Rust version.
- Scroll-down and visible-range arithmetic saturate for oversized requests.
- `sanitize_str` counts replacement spaces toward its width limit; its existing
  newline/tab preservation is retained. `sanitize_and_set` uses the bounded buffer
  writer and returns only columns actually written, with controls rendered as spaces.

## 5. Examples, tests, and documentation

- `examples/demo_native.rs` and `scripts/demo_native` demonstrate Scrin's Terminal
  API, an Aisling pane, retained partial animation frames, cursor preservation,
  bracketed paste, and a scrolling input. Ctrl+Q or Esc exits.
- `scripts/check_terminal_pty` validates the demo using Unix PTYs, exercising paste,
  partial redraws, shrinking/growing windows, tiny geometry, exit, and restoration.
  On Linux it also checks startup rollback with failing `/dev/full` output.
- `tests/rendering_regressions.rs` covers glyph transitions, clipping, styles,
  partial areas, short/erroring writers, forms, metadata, geometry, and scrolling.
  A separate screen model verifies actual ANSI output and committed buffers over
  400 deterministic randomized wide-glyph transitions, followed by a full diff.
- `tests/retained_allocations.rs` instruments allocations on the test thread and
  checks 500 frame updates/cache hits, cached-pane rendering, and core buffer reuse.
- Effect tests compare frame counts and ANSI/color output for every Aisling effect
  kind between one-shot construction and the existing builder chain. Reused effect
  buffers are poisoned with styles/wide glyphs before comparing to fresh rendering;
  loader output and storage reuse are checked over 200 updates.
- README, crate installation docs, `changes.MD`, `newest_release.MD`, and
  `for_code_tool.MD` describe the release. This handoff is included in the crate
  package. `for_code_tool.MD` and `FromKnottCode.MD` remain repository-only files.

## 6. Validation and release

Release version: **0.1.85**. Dependency version requirements remain unchanged.

Completed validation:

| Check | Result |
| --- | --- |
| `cargo fmt --all --check` and `git diff --check` | Passed |
| `cargo test --all-targets --offline` | 311 tests passed; all 11 examples compiled |
| `cargo test --doc --offline` | 3 documentation tests passed |
| `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --offline` | Passed |
| `cargo clippy --all-targets --offline` | Completed; existing style warnings remain in older code/examples |
| `scripts/check_terminal_pty` | 80x24 and 32x10 starts, paste, partial frames, 18x6 and 3x2 resize, Ctrl+Q, complete restoration passed |
| Linux initialization output failure (`/dev/full`) | Raw-mode rollback passed |
| KnottCode `cargo check --offline --bins` against this checkout | Passed using a temporary source snapshot and Cargo patch; original KnottCode files untouched |
| `cargo publish --dry-run --allow-dirty --locked` | Package verification passed; upload intentionally skipped |

The allocation tests observed zero allocations across 500 warmed same-size effect
frame changes/cache hits and 500 cached-pane frame changes with a palette. These
are allocation assertions, not frame-rate or whole-application benchmark claims.
All effect kinds passed deterministic frame/color parity against the existing
builder sequence. The previous 277 tests continue to pass alongside the additions.

Generated build artifacts are excluded from the release package and source commit.

To adopt the published release in KnottCode:

```toml
scrin = "0.1.85"
```

Then refresh the Scrin lockfile entry in the KnottCode repository. This work does
not modify KnottCode's source files, dependency declaration, or lockfile.