From 036095a1987430f71dbe85743b5ea5da396e9ca8 Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:48:04 -0500 Subject: [PATCH 1/3] sdk: tape and replay for dylib plugins A plugin loaded from the store imports `tape` and `replay` as a bundled one does (plugin.create's modules, the instances core was built with). The shape fingerprint now folds `tape.binary.version` and hashes the tape data types and replay's Stage, so a change to a tape's format moves it; recorded. tests/fixtures/tape-plugin writes, encodes, loads and plays a tape: tested linked into the app (test-integration) and as a dylib (its own build, run by scripts/check-examples.sh). Agent-Team: linux Worker: claude Co-Authored-By: Claude Sonnet 5.5 --- build/app.zig | 19 ++++++ build/sdk.zig | 3 + scripts/check-examples.sh | 5 ++ sdk/AGENTS.md | 4 ++ sdk/plugin_sdk.zig | 10 ++++ sdk/src/dylib.zig | 28 +++++++++ sdk/src/version.zig | 2 +- tests/fixtures/tape-plugin/build.zig | 16 +++++ tests/fixtures/tape-plugin/build.zig.zon | 18 ++++++ tests/fixtures/tape-plugin/play.zig | 71 +++++++++++++++++++++++ tests/fixtures/tape-plugin/plugin.zig | 22 +++++++ tests/fixtures/tape-plugin/plugin.zig.zon | 8 +++ 12 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/tape-plugin/build.zig create mode 100644 tests/fixtures/tape-plugin/build.zig.zon create mode 100644 tests/fixtures/tape-plugin/play.zig create mode 100644 tests/fixtures/tape-plugin/plugin.zig create mode 100644 tests/fixtures/tape-plugin/plugin.zig.zon diff --git a/build/app.zig b/build/app.zig index 82552bb8a..04609548f 100644 --- a/build/app.zig +++ b/build/app.zig @@ -1428,6 +1428,25 @@ fn addTests( test_integration_step.dependOn(&b.addRunArtifact(replay_tests).step); check_integration_step.dependOn(&replay_tests.step); } + // What a plugin does with `tape` and `replay` (`tests/fixtures/tape-plugin/play.zig`), as one + // linked into an app imports them: `core`'s own instances. The fixture's own build tests the + // same file as a dylib. + { + const play = b.createModule(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("tests/fixtures/tape-plugin/play.zig"), + }); + play.addImport("tape", core_module_test.import_table.get("tape").?); + play.addImport("replay", core_module_test.import_table.get("replay").?); + const play_tests = b.addTest(.{ + .name = "fizzy-tape-plugin-bundled-tests", + .root_module = play, + .filters = test_filters, + }); + test_integration_step.dependOn(&b.addRunArtifact(play_tests).step); + check_integration_step.dependOn(&play_tests.step); + } // The native backend's health counters as SDL feeds them: its log counted, and passed on to // the output it had. Needs SDL, no window. if (main_fizzy.backend) |native_backend_module| { diff --git a/build/sdk.zig b/build/sdk.zig index 40e333e70..e36688aac 100644 --- a/build/sdk.zig +++ b/build/sdk.zig @@ -59,6 +59,9 @@ pub fn wireSdkModule( sdk_module.addImport("dvui", dvui_module); sdk_module.addImport("proxy_bridge", proxy_bridge_module); sdk_module.addImport("core", core_module); + // Their data types are on the plugin boundary (`sdk/src/dylib.zig`). + sdk_module.addImport("tape", core_module.import_table.get("tape").?); + sdk_module.addImport("replay", core_module.import_table.get("replay").?); // `sdk_version` as a *named* module rather than a relative `@import`: `sdk/sdk_version.zig` // is the single edit site for the triplet, but it sits outside this module's root // (`sdk/src/`), and Zig confines a module's relative imports to its own root — a named diff --git a/scripts/check-examples.sh b/scripts/check-examples.sh index 7295127e6..67aa1a7f1 100755 --- a/scripts/check-examples.sh +++ b/scripts/check-examples.sh @@ -118,3 +118,8 @@ for b in $builds; do done echo "== example-plugin and example-app, against this tree" printf '%s\n' $builds | xargs -P "${EXAMPLES_JOBS:-3}" -n 1 bash -c 'build_one "$1"' _ + +# A plugin that imports `tape` and `replay` builds and plays a tape as a dylib too +# (tests/fixtures/tape-plugin; the same file is tested linked into the app by `test-integration`). +echo "== tape-plugin fixture, as a dylib" +(cd "$root/tests/fixtures/tape-plugin" && zig build test --cache-dir "$work/tape-plugin-cache" --prefix "$work/tape-plugin-out") diff --git a/sdk/AGENTS.md b/sdk/AGENTS.md index e0ad42b7a..e0b41e0ee 100644 --- a/sdk/AGENTS.md +++ b/sdk/AGENTS.md @@ -9,6 +9,10 @@ file is the rules for changing it. `sdk_version.zig` outside a release PR (`CONTRIBUTING.md`, "Changing the SDK"). - **A new boundary type goes in `sdk_boundary_types`** (`src/dylib.zig`), or the fingerprint never sees it change. +- **`tape` and `replay` are boundary too.** `tape.binary.version` is folded into the shape + fingerprint and the tape data types are in `tape_boundary_types` (`src/dylib.zig`): bump the version or + change one, and the fingerprint moves. A store plugin imports both, as a bundled one does + (`plugin.create`; `tests/fixtures/tape-plugin`). - **This directory ships on its own** as `fizzy-sdk-v*.tar.gz` (`scripts/pack-sdk.sh`), holding only what `build.zig.zon`'s `.paths` lists. A new top-level file here goes in that list too; `scripts/check-examples.sh` builds the example plugin from the packed tarball and catches it. diff --git a/sdk/plugin_sdk.zig b/sdk/plugin_sdk.zig index 287cdc616..787a0f6d1 100644 --- a/sdk/plugin_sdk.zig +++ b/sdk/plugin_sdk.zig @@ -43,6 +43,9 @@ pub const Modules = struct { sdk: *std.Build.Module, dvui: *std.Build.Module, proxy_bridge: *std.Build.Module, + /// `core`'s own `tape` and `replay`, the instances a statically linked plugin imports. + tape: *std.Build.Module, + replay: *std.Build.Module, }; pub const ModulesOptions = struct { @@ -164,6 +167,8 @@ fn modulesFromDep(fizzy_dep: *std.Build.Dependency) Modules { .sdk = fizzy_dep.module("fizzy_sdk"), .dvui = fizzy_dep.module("dvui"), .proxy_bridge = fizzy_dep.module("proxy_bridge"), + .tape = fizzy_dep.module("tape"), + .replay = fizzy_dep.module("replay"), }; } @@ -176,6 +181,8 @@ pub fn addImports(mod: *std.Build.Module, plugin_modules: Modules) void { mod.addImport("fizzy_sdk", plugin_modules.sdk); mod.addImport("dvui", plugin_modules.dvui); mod.addImport("proxy_bridge", plugin_modules.proxy_bridge); + mod.addImport("tape", plugin_modules.tape); + mod.addImport("replay", plugin_modules.replay); } fn module( @@ -662,6 +669,9 @@ pub fn exportModules( sdk_mod.addImport("dvui", dvui_proxy_mod); sdk_mod.addImport("proxy_bridge", proxy_bridge_mod); sdk_mod.addImport("core", core_mod); + // The fingerprint hashes `tape`'s and `replay`'s data types (`dylib.zig`). + sdk_mod.addImport("tape", core_mod.import_table.get("tape").?); + sdk_mod.addImport("replay", core_mod.import_table.get("replay").?); // See the matching comment in `build/sdk.zig`. `sdk_version.zig` needs no `repoPath` hop: // unlike `src/…`, it lives at this package's root in *both* layouts (in-repo `fizzy/sdk/` // and the release tarball, whose root is that same directory). diff --git a/sdk/src/dylib.zig b/sdk/src/dylib.zig index 2ac1adc19..f1dcdac23 100644 --- a/sdk/src/dylib.zig +++ b/sdk/src/dylib.zig @@ -13,6 +13,8 @@ const builtin = @import("builtin"); const dvui = @import("dvui"); const proxy_bridge = @import("proxy_bridge"); const core = @import("core"); +const tape = @import("tape"); +const replay = @import("replay"); const fingerprint = @import("fingerprint.zig"); const dvui_context = @import("dvui_context.zig"); const runtime = @import("runtime.zig"); @@ -189,6 +191,30 @@ const sdk_boundary_types = .{ VersionTriplet, }; +/// A plugin's demo crosses the boundary as bytes (`Demo.tape`), so these are shared by what they +/// spell, not by pointer: `tape.binary.version` is folded in `sdk_shape_fingerprint`, and the +/// data types a tape is made of are hashed here. `Stage` is the seam a plugin's app fills in. +const tape_boundary_types = .{ + tape.Tape, + tape.Tape.Op, + tape.Tape.Action, + tape.Tape.Button, + tape.Tape.Command, + tape.Tape.Step, + tape.Tape.Scroll, + tape.Tape.Target, + tape.Tape.Wait, + tape.Tape.Skip, + tape.Tape.Until, + tape.Tape.Caption, + tape.Tape.Chapter, + tape.Tape.Keyframe, + tape.Tape.Setting, + tape.Tape.File, + replay.Stage, + replay.Stage.VTable, +}; + const entry_symbol_types = .{ RegisterFn, SetGlobalsFn, @@ -280,6 +306,8 @@ pub const sdk_shape_fingerprint: u64 = blk: { h = fingerprint.hashAllShape(h, sdk_boundary_types, 6); h = fingerprint.hashAllShape(h, entry_symbol_types, 3); h = fingerprint.hashAllShape(h, dvui_shared_state_types, 6); + h = fingerprint.hashAllShape(h, tape_boundary_types, 6); + h = fingerprint.foldString(h, std.mem.asBytes(&tape.binary.version)); break :blk h; }; diff --git a/sdk/src/version.zig b/sdk/src/version.zig index dd33bb112..68218fe91 100644 --- a/sdk/src/version.zig +++ b/sdk/src/version.zig @@ -75,7 +75,7 @@ pub const sdk_version = @import("sdk_version").sdk_version; /// why it is a single target/mode-invariant literal rather than a per-target table. Update this /// value from the `@compileError` it triggers whenever it changes; leave `sdk_version` to the /// next SDK release. -pub const recorded_sdk_shape_fingerprint: u64 = 0x68bbdb31985c4555; +pub const recorded_sdk_shape_fingerprint: u64 = 0x30120592d24f1a35; comptime { if (dylib.sdk_shape_fingerprint != recorded_sdk_shape_fingerprint) { diff --git a/tests/fixtures/tape-plugin/build.zig b/tests/fixtures/tape-plugin/build.zig new file mode 100644 index 000000000..a74849aa4 --- /dev/null +++ b/tests/fixtures/tape-plugin/build.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const fizzy = @import("fizzy"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const plugin = fizzy.plugin.create(b, .{ .target = target, .optimize = optimize }); + + // The plugin's module as the dylib sees it, tested: `tape` and `replay` resolve in a store + // plugin as they do in a bundled one (`build/app.zig` tests `play.zig` the other way). + const tests = b.addTest(.{ .name = "tape-fixture-tests", .root_module = plugin.module }); + const test_step = b.step("test", "Run the fixture's tests as a dylib plugin sees its imports"); + test_step.dependOn(&b.addRunArtifact(tests).step); + test_step.dependOn(&plugin.lib.step); +} diff --git a/tests/fixtures/tape-plugin/build.zig.zon b/tests/fixtures/tape-plugin/build.zig.zon new file mode 100644 index 000000000..759cf5868 --- /dev/null +++ b/tests/fixtures/tape-plugin/build.zig.zon @@ -0,0 +1,18 @@ +.{ + .name = .tape_fixture, + .version = "0.1.0", + .minimum_zig_version = "0.17.0", + .paths = .{ + "build.zig", + "build.zig.zon", + "plugin.zig", + "plugin.zig.zon", + "play.zig", + }, + .fingerprint = 0x39f10a95729a0ebe, + .dependencies = .{ + .fizzy = .{ + .path = "../../../sdk", + }, + }, +} diff --git a/tests/fixtures/tape-plugin/play.zig b/tests/fixtures/tape-plugin/play.zig new file mode 100644 index 000000000..f5f0965e7 --- /dev/null +++ b/tests/fixtures/tape-plugin/play.zig @@ -0,0 +1,71 @@ +//! A plugin's use of `tape` and `replay`: write a demo with `Script`, ship it as bytes, load it and +//! play it. The same file is tested twice: linked into an app (`build/app.zig`, the imports +//! `plugins.staticModule` gives) and built as a plugin dylib (this directory's `build.zig`, the +//! imports `plugin.create` gives), which is how a plugin's one source builds both ways. +const std = @import("std"); +const tape = @import("tape"); +const replay = @import("replay"); + +const Played = struct { + commands: usize = 0, + + fn sink(self: *Played) tape.Sequencer.Sink { + return .{ .ctx = self, .vtable = &.{ + .locate = locate, + .moveTo = moveTo, + .button = button, + .scroll = scroll, + .key = key, + .text = text, + .command = command, + .keyframe = keyframe, + .holds = holds, + .failed = failed, + } }; + } + fn locate(_: *anyopaque, _: tape.Tape.Target) ?tape.Sequencer.Point { + return null; + } + fn moveTo(_: *anyopaque, _: tape.Sequencer.Point) void {} + fn button(_: *anyopaque, _: tape.Tape.Button, _: bool) void {} + fn scroll(_: *anyopaque, _: tape.Tape.Scroll) void {} + fn key(_: *anyopaque, _: []const u8) void {} + fn text(_: *anyopaque, _: []const u8) void {} + fn command(ctx: *anyopaque, cmd: tape.Tape.Command) void { + const self: *Played = @ptrCast(@alignCast(ctx)); + if (std.mem.eql(u8, cmd.id, "fixture.hello")) self.commands += 1; + } + fn keyframe(_: *anyopaque, _: *const tape.Tape.Keyframe) void {} + fn holds(_: *anyopaque, _: tape.Tape.Until) bool { + return true; + } + fn failed(_: *anyopaque, _: tape.Sequencer.Failure) void {} +}; + +test "a plugin writes a demo, ships it as bytes, and plays it back" { + const gpa = std.testing.allocator; + var script: tape.Script = .init(gpa, "fixture", "Fixture"); + errdefer script.deinit(); + try script.keyframe(.{ .root = "demo://fixture" }); + try script.command("fixture.hello"); + var owned = try script.finish(); + defer owned.deinit(); + + const bytes = try tape.binary.encode(gpa, owned.tape, .{}); + defer gpa.free(bytes); + var loaded = try tape.Tape.load(gpa, bytes, .{}); + defer loaded.deinit(); + + var played: Played = .{}; + var seq: tape.Sequencer = .init(&loaded.tape); + var frames: usize = 0; + while (!seq.done() and frames < 1_000) : (frames += 1) { + _ = seq.advance(seq.now + 16, 16, played.sink()); + } + try std.testing.expectEqual(@as(usize, 1), played.commands); +} + +test "replay is the module that drives a window with a tape" { + // The player takes the tape `tape` made: one `Tape` type on both sides of the import. + try std.testing.expect(@hasDecl(replay.Player, "play")); +} diff --git a/tests/fixtures/tape-plugin/plugin.zig b/tests/fixtures/tape-plugin/plugin.zig new file mode 100644 index 000000000..4f3389782 --- /dev/null +++ b/tests/fixtures/tape-plugin/plugin.zig @@ -0,0 +1,22 @@ +//! Fixture: a plugin that imports `tape` and `replay`, built as a dylib to show a store plugin gets +//! both (`play.zig` holds what it does with them). Registers nothing but itself. +const sdk = @import("fizzy_sdk"); + +pub const plugin_options = @import("fizzy_plugin_options"); + +var plugin: sdk.Plugin = .{ + .state = undefined, + .vtable = &vtable, + .id = plugin_options.id, + .display_name = plugin_options.name, +}; + +const vtable: sdk.Plugin.VTable = .{}; + +pub fn register(host: *sdk.Host) !void { + try host.registerPlugin(&plugin); +} + +test { + _ = @import("play.zig"); +} diff --git a/tests/fixtures/tape-plugin/plugin.zig.zon b/tests/fixtures/tape-plugin/plugin.zig.zon new file mode 100644 index 000000000..f0f6d5e85 --- /dev/null +++ b/tests/fixtures/tape-plugin/plugin.zig.zon @@ -0,0 +1,8 @@ +.{ + .id = "tape_fixture", + .name = "Tape fixture", + .version = "0.1.0", + .min_sdk_version = "", + .description = "Imports tape and replay, as a dylib.", + .tags = .{"fixture"}, +} From 1c01158393a3091d54c1cb34949f73862a8b0c9e Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:58:00 -0500 Subject: [PATCH 2/3] sdk: tape-replay-plugins review fixes Hash Tape.Check, the Sequencer sink types and replay.Player in the boundary fingerprint, build the bundled fixture through its own staticModule and rehome, and scope Script's errdefer so a failure after finish cannot free the arena twice. Agent-Team: linux Worker: claude Co-Authored-By: Claude Sonnet 5.5 --- build.zig.zon | 4 +++ build/app.zig | 52 ++++++++++++++++++---------- sdk/src/dylib.zig | 6 ++++ sdk/src/version.zig | 2 +- tests/fixtures/tape-plugin/build.zig | 5 +++ tests/fixtures/tape-plugin/play.zig | 13 ++++--- 6 files changed, 57 insertions(+), 25 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 5132dc046..60f7462a5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -27,6 +27,10 @@ .fizzy_sdk = .{ .path = "sdk/", }, + // A plugin that imports `tape` and `replay`, linked into the test build as an app would. + .tape_fixture = .{ + .path = "tests/fixtures/tape-plugin", + }, // Fizzy's native backend (SDL3, its GPU renderer, the platform pieces), with SDL, // zig-objc and zigwin32 pinned there. App-only: plugins never link it. .fizzy_backend = .{ diff --git a/build/app.zig b/build/app.zig index 04609548f..cbc60391c 100644 --- a/build/app.zig +++ b/build/app.zig @@ -1428,25 +1428,6 @@ fn addTests( test_integration_step.dependOn(&b.addRunArtifact(replay_tests).step); check_integration_step.dependOn(&replay_tests.step); } - // What a plugin does with `tape` and `replay` (`tests/fixtures/tape-plugin/play.zig`), as one - // linked into an app imports them: `core`'s own instances. The fixture's own build tests the - // same file as a dylib. - { - const play = b.createModule(.{ - .target = target, - .optimize = optimize, - .root_source_file = b.path("tests/fixtures/tape-plugin/play.zig"), - }); - play.addImport("tape", core_module_test.import_table.get("tape").?); - play.addImport("replay", core_module_test.import_table.get("replay").?); - const play_tests = b.addTest(.{ - .name = "fizzy-tape-plugin-bundled-tests", - .root_module = play, - .filters = test_filters, - }); - test_integration_step.dependOn(&b.addRunArtifact(play_tests).step); - check_integration_step.dependOn(&play_tests.step); - } // The native backend's health counters as SDL feeds them: its log counted, and passed on to // the output it had. Needs SDL, no window. if (main_fizzy.backend) |native_backend_module| { @@ -1501,6 +1482,39 @@ fn addTests( const fixture_test = b.addTest(.{ .name = "rehome-fixture", .root_module = module }); test_step.dependOn(&b.addRunArtifact(fixture_test).step); } + // What a plugin does with `tape` and `replay` (`tests/fixtures/tape-plugin/play.zig`), through + // the path an application's plugin takes: its own `staticModule`, then `rehome` onto this + // executable's framework. The fixture's own build tests the same file as a dylib. + { + const fixture_dep = b.dependency("tape_fixture", .{}); + const source = @import("tape_fixture").staticModule(fixture_dep.builder, .{ + .target = target, + .optimize = optimize, + .dvui = dvui_testing_dep.module("dvui_testing"), + .core = core_module_test, + .sdk = sdk_module_test, + .icons = icons_test, + }); + const consumer = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true }); + const module = @import("plugins.zig").rehome(b, .{ + .target = target, + .optimize = optimize, + .dvui = dvui_testing_dep.module("dvui_testing"), + .core = core_module_test, + .sdk = sdk_module_test, + .icons = icons_test, + .backend = null, + .workbench_opts = workbench_opts, + .consumer = consumer, + }, "tape_fixture", source); + const play_tests = b.addTest(.{ + .name = "fizzy-tape-plugin-bundled-tests", + .root_module = module, + .filters = test_filters, + }); + test_integration_step.dependOn(&b.addRunArtifact(play_tests).step); + check_integration_step.dependOn(&play_tests.step); + } const workbench_module_test = workbench_plugin.addStaticModule(b, target, optimize, .{ .dvui = dvui_testing_dep.module("dvui_testing"), .core = core_module_test, diff --git a/sdk/src/dylib.zig b/sdk/src/dylib.zig index f1dcdac23..216d5259d 100644 --- a/sdk/src/dylib.zig +++ b/sdk/src/dylib.zig @@ -211,6 +211,12 @@ const tape_boundary_types = .{ tape.Tape.Keyframe, tape.Tape.Setting, tape.Tape.File, + tape.Tape.Check, + tape.Sequencer.Point, + tape.Sequencer.Failure, + tape.Sequencer.Sink, + tape.Sequencer.Sink.VTable, + replay.Player, replay.Stage, replay.Stage.VTable, }; diff --git a/sdk/src/version.zig b/sdk/src/version.zig index 68218fe91..18b165d3c 100644 --- a/sdk/src/version.zig +++ b/sdk/src/version.zig @@ -75,7 +75,7 @@ pub const sdk_version = @import("sdk_version").sdk_version; /// why it is a single target/mode-invariant literal rather than a per-target table. Update this /// value from the `@compileError` it triggers whenever it changes; leave `sdk_version` to the /// next SDK release. -pub const recorded_sdk_shape_fingerprint: u64 = 0x30120592d24f1a35; +pub const recorded_sdk_shape_fingerprint: u64 = 0x9ebefb27b154a9da; comptime { if (dylib.sdk_shape_fingerprint != recorded_sdk_shape_fingerprint) { diff --git a/tests/fixtures/tape-plugin/build.zig b/tests/fixtures/tape-plugin/build.zig index a74849aa4..0df47671c 100644 --- a/tests/fixtures/tape-plugin/build.zig +++ b/tests/fixtures/tape-plugin/build.zig @@ -1,6 +1,11 @@ const std = @import("std"); const fizzy = @import("fizzy"); +/// The plugin as an application links it (`build/app.zig` builds it this way and plays its tests). +pub fn staticModule(b: *std.Build, framework: anytype) *std.Build.Module { + return fizzy.plugin.staticModule(b, framework, .{}); +} + pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); diff --git a/tests/fixtures/tape-plugin/play.zig b/tests/fixtures/tape-plugin/play.zig index f5f0965e7..ca6d89696 100644 --- a/tests/fixtures/tape-plugin/play.zig +++ b/tests/fixtures/tape-plugin/play.zig @@ -44,11 +44,14 @@ const Played = struct { test "a plugin writes a demo, ships it as bytes, and plays it back" { const gpa = std.testing.allocator; - var script: tape.Script = .init(gpa, "fixture", "Fixture"); - errdefer script.deinit(); - try script.keyframe(.{ .root = "demo://fixture" }); - try script.command("fixture.hello"); - var owned = try script.finish(); + var owned = owned: { + // `finish` hands the arena to `owned`, so `script` owns it only until then. + var script: tape.Script = .init(gpa, "fixture", "Fixture"); + errdefer script.deinit(); + try script.keyframe(.{ .root = "demo://fixture" }); + try script.command("fixture.hello"); + break :owned try script.finish(); + }; defer owned.deinit(); const bytes = try tape.binary.encode(gpa, owned.tape, .{}); From 0f3c206c83bc2860b54d8a4393156b7711e8ba32 Mon Sep 17 00:00:00 2001 From: "fizzy-linux[bot]" <346940526+fizzy-linux[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 11:25:03 -0500 Subject: [PATCH 3/3] tests: give the tape-plugin fixture's anytype its reason Agent-Team: linux Worker: claude Co-Authored-By: Claude Sonnet 5.5 --- tests/fixtures/tape-plugin/build.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/fixtures/tape-plugin/build.zig b/tests/fixtures/tape-plugin/build.zig index 0df47671c..67a6ed6e0 100644 --- a/tests/fixtures/tape-plugin/build.zig +++ b/tests/fixtures/tape-plugin/build.zig @@ -2,6 +2,7 @@ const std = @import("std"); const fizzy = @import("fizzy"); /// The plugin as an application links it (`build/app.zig` builds it this way and plays its tests). +// anytype: the app's framework struct, whose type `fizzy.plugin.staticModule` owns. pub fn staticModule(b: *std.Build, framework: anytype) *std.Build.Module { return fizzy.plugin.staticModule(b, framework, .{}); }