diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index 0f42c5a6..9089079a 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -18,14 +18,21 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Zig - uses: mlugg/setup-zig@v2 + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 with: version: 0.15.2 - - name: Build escape-hatch - run: | - cd idaptik-escape-hatch - zig build + - name: Build top-level Zig FFI + run: zig build test + working-directory: ffi/zig + + - name: Build idaptik-ums Zig FFI + run: zig build test + working-directory: idaptik-ums/ffi/zig + + - name: Build VM (wasm32-freestanding) + run: zig build + working-directory: vm/wasm diff --git a/.github/workflows/e2e-playwright.yml b/.github/workflows/e2e-playwright.yml index 3b85603f..4977d819 100644 --- a/.github/workflows/e2e-playwright.yml +++ b/.github/workflows/e2e-playwright.yml @@ -47,7 +47,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Deno - uses: denoland/setup-deno@5fae568d37c3b73e0e4ca63d4e2c4e324a2b3497 # v2 + uses: denoland/setup-deno@4606d5cc6fb3f673efd4f594850e3f4b3e9d29cd # v2.0.0 with: deno-version: v2.x diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 4f0a286f..254dc735 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -23,15 +23,15 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Zig - uses: mlugg/setup-zig@v2 + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 with: version: 0.15.2 - name: Run VM fuzz tests run: | - cd tests/fuzz - timeout 300 zig build fuzz -- --max_total_time=240 2>/dev/null || true + timeout 300 zig test --fuzz tests/fuzz/fuzz_vm_instruction.zig -- --max_total_time=240 2>/dev/null || true + timeout 300 zig test --fuzz tests/fuzz/fuzz_level_config.zig -- --max_total_time=240 2>/dev/null || true continue-on-error: true diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index f94a602b..6c8f59fe 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -33,37 +33,45 @@ jobs: name: rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: components: rustfmt - run: cargo fmt --all -- --check + working-directory: idaptik-developers/src/escape-hatch clippy: name: clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: components: clippy - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + workspaces: idaptik-developers/src/escape-hatch - run: cargo clippy --all-targets -- -D warnings + working-directory: idaptik-developers/src/escape-hatch test: name: cargo test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + workspaces: idaptik-developers/src/escape-hatch - run: cargo test --no-fail-fast + working-directory: idaptik-developers/src/escape-hatch audit: name: cargo audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: rustsec/audit-check@v2.0.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} + working-directory: idaptik-developers/src/escape-hatch diff --git a/.hypatia-baseline.json b/.hypatia-baseline.json new file mode 100644 index 00000000..adbdca08 --- /dev/null +++ b/.hypatia-baseline.json @@ -0,0 +1,38 @@ +[ + { + "severity": "high", + "rule_module": "cicd_rules", + "type": "banned_language_file", + "file_pattern": "**/*.res", + "tracking_issue": "hyperpolymath/idaptik#84", + "note": "ReScript → AffineScript megaport. Tracked at hyperpolymath/idaptik#84 (tech-debt parent) and hyperpolymath/standards#252 (estate umbrella) / standards#279 (STEP 8). Migration assistant at hyperpolymath/affinescript/tools/res-to-affine/ is in flight (affinescript#57); upstream blockers include affinescript#160/#161/#162 (Http/Json/Dict primitives) and affinescript#59 (effect-row Async/IO/Throws). Exemption clears as files convert.", + "expires_at": "2027-06-01" + }, + { + "severity": "high", + "rule_module": "cicd_rules", + "type": "banned_language_file", + "file_pattern": "**/*.res.mjs", + "tracking_issue": "hyperpolymath/idaptik#84", + "note": "ReScript-compiled output sibling to **/*.res. In-source compilation is the repo convention (esmodule mode); these clear automatically as .res files port to .affine. See parent exemption above.", + "expires_at": "2027-06-01" + }, + { + "severity": "high", + "rule_module": "cicd_rules", + "type": "banned_language_file", + "file_pattern": "dlc/**/*.ts", + "tracking_issue": "hyperpolymath/idaptik#84", + "note": "DLC packs use TypeScript; estate-wide TS→AffineScript campaign is hyperpolymath/standards#254. These files port via the same megaport pipeline once affinescript stdlib AOT coherence (affinescript#128/#136) lands.", + "expires_at": "2027-06-01" + }, + { + "severity": "high", + "rule_module": "cicd_rules", + "type": "banned_language_file", + "file_pattern": "dlc/idaptik-dlc-reversible/robot-repo-bot/_modules/robot_repo.py", + "tracking_issue": "hyperpolymath/idaptik-dlc-reversible#robot-repo-bot-py-removal", + "note": "SaltStack execution module inside the dlc/idaptik-dlc-reversible git submodule (origin git@gitlab.com:hyperpolymath/idaptik-dlc-reversible.git). The estate-wide Python ban removed the SaltStack carveout on 2026-01-03 — this file is residual debt belonging to the submodule, not the idaptik parent. Fundamental fix path: rewrite robot-repo-bot in shell/Just/Rust inside the submodule repo and bump the gitlink pointer; until then this exemption acknowledges the cross-repo ownership boundary. Sibling guard: third-party submodule content is not idaptik's responsibility to migrate.", + "expires_at": "2026-12-01" + } +] diff --git a/.hypatia-ignore b/.hypatia-ignore new file mode 100644 index 00000000..64509642 --- /dev/null +++ b/.hypatia-ignore @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (c) Jonathan D.A. Jewell +# +# .hypatia-ignore — legacy exemption file for banned-config-file checks. +# +# This file complements .hypatia-baseline.json (the canonical format for +# banned-language-file and other rule-modules). The banned_config_file +# rule in hyperpolymath/standards governance-reusable.yml ONLY honours +# the legacy .hypatia-ignore flat-file format (it does NOT read the +# baseline JSON), so configs like rescript.json must be exempted here +# even though .res file exemptions live in the JSON baseline. +# +# Each line: : +# rules: cicd_rules/banned_config_file, cicd_rules/banned_language_file + +# rescript.json — root ReScript build config, retained while the +# .res → .affine megaport is in flight. Tracked by idaptik#84 +# (tech-debt parent). Clears when the root src/ tree converts to +# .affine and AffineScript replaces rescript as the build coordinator. +cicd_rules/banned_config_file:rescript.json diff --git a/.machine_readable/anchors/ANCHOR.a2ml b/.machine_readable/anchors/ANCHOR.a2ml index c614d578..afb6bd66 100644 --- a/.machine_readable/anchors/ANCHOR.a2ml +++ b/.machine_readable/anchors/ANCHOR.a2ml @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (c) Jonathan D.A. Jewell # ⚓ ANCHOR: idaptik # This is the canonical authority for the idaptik repository. diff --git a/audits/assail-classifications.a2ml b/audits/assail-classifications.a2ml index 6292c00e..06e818cf 100644 --- a/audits/assail-classifications.a2ml +++ b/audits/assail-classifications.a2ml @@ -1,87 +1,97 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -;; -;; Assail Classifications — idaptik -;; See panic-attack/.claude/CLAUDE.md § "User-Classification Registry". +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (c) Jonathan D.A. Jewell +# +# Assail Classifications — idaptik +# See panic-attack/.claude/CLAUDE.md § "User-Classification Registry". -(assail-classifications - (metadata - (version "1.0.0") - (project "idaptik") - (last-updated "2026-05-26") - (entries 12) - (status "active")) +[metadata] +project = "idaptik" +schema_version = "1.0.0" +version = "1.0.0" +last-updated = "2026-05-26" +entries = 12 +status = "active" - (classification - (file "src/app/tools/PasswordCracker.res") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "src/app/tools/PasswordCracker.res.mjs") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "src/app/devices/GlobalNetworkData.res") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "src/app/devices/GlobalNetworkData.res.mjs") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "tests/unit/tools/PasswordCracker_test.mjs") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "main-game/dist/assets/index-Cdt-JTFK.js") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "lib/bs/src/app/tools/PasswordCracker.res") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "lib/bs/src/app/tools/PasswordCracker.res.mjs") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "lib/bs/src/app/devices/GlobalNetworkData.res") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "lib/bs/src/app/devices/GlobalNetworkData.res.mjs") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "lib/ocaml/PasswordCracker.res") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) - (classification - (file "lib/ocaml/GlobalNetworkData.res") - (category "HardcodedSecret") - (classification "game-content-fixture") - (audit "audits/audit-game-content-fixture-2026-05-26.md") - (rationale "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE.")) -) +[[classification]] +file = "src/app/tools/PasswordCracker.res" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "src/app/tools/PasswordCracker.res.mjs" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "src/app/devices/GlobalNetworkData.res" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "src/app/devices/GlobalNetworkData.res.mjs" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "tests/unit/tools/PasswordCracker_test.mjs" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "main-game/dist/assets/index-Cdt-JTFK.js" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "lib/bs/src/app/tools/PasswordCracker.res" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "lib/bs/src/app/tools/PasswordCracker.res.mjs" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "lib/bs/src/app/devices/GlobalNetworkData.res" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "lib/bs/src/app/devices/GlobalNetworkData.res.mjs" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "lib/ocaml/PasswordCracker.res" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." + +[[classification]] +file = "lib/ocaml/GlobalNetworkData.res" +category = "HardcodedSecret" +classification = "game-content-fixture" +audit = "audits/audit-game-content-fixture-2026-05-26.md" +rationale = "Fictional in-game passwords / credentials for the hacker-themed gameplay; not real secrets. GlobalNetworkData.res carries the explicit SECURITY NOTE." diff --git a/deno.json b/deno.json index 47211055..4a899774 100644 --- a/deno.json +++ b/deno.json @@ -25,6 +25,7 @@ }, "imports": { "@assetpack/core": "npm:@assetpack/core@1.7.0", + "@playwright/test": "npm:@playwright/test@1.60.0", "@rescript/core": "npm:@rescript/core@1.6.1", "@rescript/runtime": "npm:@rescript/runtime@12.2.0", "rescript": "npm:rescript@12.2.0", diff --git a/ffi/zig/build.zig b/ffi/zig/build.zig index 4c9f2e78..3f39f64a 100644 --- a/ffi/zig/build.zig +++ b/ffi/zig/build.zig @@ -1,11 +1,25 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (c) Jonathan D.A. Jewell const std = @import("std"); + pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const lib = b.addStaticLibrary(.{ .name = "idaptik_ffi", .root_source_file = b.path("src/idaptik_ffi.zig"), .target = target, .optimize = optimize }); + + const root_module = b.createModule(.{ + .root_source_file = b.path("src/idaptik_ffi.zig"), + .target = target, + .optimize = optimize, + }); + + const lib = b.addLibrary(.{ + .name = "idaptik_ffi", + .linkage = .static, + .root_module = root_module, + }); b.installArtifact(lib); - const tests = b.addTest(.{ .root_source_file = b.path("src/idaptik_ffi.zig"), .target = target, .optimize = optimize }); + + const tests = b.addTest(.{ .root_module = root_module }); const test_step = b.step("test", "Run FFI tests"); test_step.dependOn(&b.addRunArtifact(tests).step); } diff --git a/ffi/zig/src/idaptik_ffi.zig b/ffi/zig/src/idaptik_ffi.zig index 78bc22bc..27222fef 100644 --- a/ffi/zig/src/idaptik_ffi.zig +++ b/ffi/zig/src/idaptik_ffi.zig @@ -9,17 +9,17 @@ pub const PlayerRole = enum(i32) { jessica = 0, q = 1 }; pub const SyncResult = enum(i32) { ok = 0, conflict = 1, stale = 2, @"error" = 3 }; /// Validate level ID is in bounds (0-199). -pub export fn idaptik_valid_level(level_id: i32) callconv(.C) i32 { +pub export fn idaptik_valid_level(level_id: i32) callconv(.c) i32 { return if (level_id >= 0 and level_id < 200) 1 else 0; } /// Check if two roles are different (asymmetric co-op requires different roles). -pub export fn idaptik_roles_disjoint(role_a: i32, role_b: i32) callconv(.C) i32 { +pub export fn idaptik_roles_disjoint(role_a: i32, role_b: i32) callconv(.c) i32 { return if (role_a != role_b) 1 else 0; } /// Validate sync operation ID. -pub export fn idaptik_valid_sync_op(op: i32) callconv(.C) i32 { +pub export fn idaptik_valid_sync_op(op: i32) callconv(.c) i32 { return if (op >= 0 and op <= 3) 1 else 0; } diff --git a/idaptik-developers/src/escape-hatch/src/app.rs b/idaptik-developers/src/escape-hatch/src/app.rs index 6175ab7b..0782d8e9 100644 --- a/idaptik-developers/src/escape-hatch/src/app.rs +++ b/idaptik-developers/src/escape-hatch/src/app.rs @@ -65,12 +65,16 @@ pub fn commands_for_tab(tab: ActiveTab) -> Vec { CommandEntry { label: "System Status", description: "Show host info, Deno version, Podman version", - shell_cmd: Some("echo '--- Host ---' && uname -a && echo && echo '--- Deno ---' && deno --version && echo && echo '--- Podman ---' && podman --version"), + shell_cmd: Some( + "echo '--- Host ---' && uname -a && echo && echo '--- Deno ---' && deno --version && echo && echo '--- Podman ---' && podman --version", + ), }, CommandEntry { label: "Game Status", description: "Show ReScript build status and asset manifest", - shell_cmd: Some("cd /var/mnt/eclipse/repos/idaptik && deno task res:build 2>&1 | tail -5 && echo && echo '--- Assets ---' && wc -l src/manifest.json"), + shell_cmd: Some( + "cd /var/mnt/eclipse/repos/idaptik && deno task res:build 2>&1 | tail -5 && echo && echo '--- Assets ---' && wc -l src/manifest.json", + ), }, CommandEntry { label: "Recent Activity", @@ -92,12 +96,16 @@ pub fn commands_for_tab(tab: ActiveTab) -> Vec { CommandEntry { label: "Build UMS (Tauri)", description: "Build the Unified Modding Studio desktop app", - shell_cmd: Some("cd /var/mnt/eclipse/repos/idaptik/idaptik-ums && cargo tauri build 2>&1"), + shell_cmd: Some( + "cd /var/mnt/eclipse/repos/idaptik/idaptik-ums && cargo tauri build 2>&1", + ), }, CommandEntry { label: "Build Escape Hatch", description: "cargo build — compile this TUI from source", - shell_cmd: Some("cd /var/mnt/eclipse/repos/idaptik/idaptik-developers/src/escape-hatch && cargo build 2>&1"), + shell_cmd: Some( + "cd /var/mnt/eclipse/repos/idaptik/idaptik-developers/src/escape-hatch && cargo build 2>&1", + ), }, ], ActiveTab::Test => vec![ @@ -131,7 +139,9 @@ pub fn commands_for_tab(tab: ActiveTab) -> Vec { CommandEntry { label: "List Containers", description: "podman ps -a — show all containers", - shell_cmd: Some("podman ps -a --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'"), + shell_cmd: Some( + "podman ps -a --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'", + ), }, CommandEntry { label: "Compose Up", @@ -146,12 +156,16 @@ pub fn commands_for_tab(tab: ActiveTab) -> Vec { CommandEntry { label: "Container Logs", description: "podman-compose logs --tail=40 — recent log output", - shell_cmd: Some("cd /var/mnt/eclipse/repos/idaptik && podman-compose logs --tail=40 2>&1"), + shell_cmd: Some( + "cd /var/mnt/eclipse/repos/idaptik && podman-compose logs --tail=40 2>&1", + ), }, CommandEntry { label: "Container Health", description: "podman healthcheck run on all running containers", - shell_cmd: Some("for c in $(podman ps -q); do echo \"=== $(podman inspect --format '{{.Name}}' $c) ===\"; podman healthcheck run $c 2>&1 || echo 'no healthcheck'; done"), + shell_cmd: Some( + "for c in $(podman ps -q); do echo \"=== $(podman inspect --format '{{.Name}}' $c) ===\"; podman healthcheck run $c 2>&1 || echo 'no healthcheck'; done", + ), }, ], ActiveTab::Tools => vec![ @@ -557,10 +571,10 @@ fn extract_test_count(output: &str) -> Option { for line in output.lines().rev() { // Pattern: "N/N tests" or "N tests passed" for word in line.split_whitespace() { - if let Ok(n) = word.trim_end_matches('/').parse::() { - if line.contains("pass") || line.contains("test") { - return Some(n); - } + if let Ok(n) = word.trim_end_matches('/').parse::() + && (line.contains("pass") || line.contains("test")) + { + return Some(n); } } } diff --git a/idaptik-developers/src/escape-hatch/src/ui.rs b/idaptik-developers/src/escape-hatch/src/ui.rs index 7400c7f7..eb1edd38 100644 --- a/idaptik-developers/src/escape-hatch/src/ui.rs +++ b/idaptik-developers/src/escape-hatch/src/ui.rs @@ -25,7 +25,7 @@ pub fn draw(frame: &mut Frame, app: &App) { .constraints([ Constraint::Length(1), // title Constraint::Length(2), // tabs - Constraint::Min(0), // body + Constraint::Min(0), // body Constraint::Length(3), // status bar ]) .split(frame.area()); @@ -155,7 +155,11 @@ fn draw_component_list(frame: &mut Frame, area: Rect, app: &App) { /// Render a detail gauge for the first dashboard component (informational). fn draw_component_detail(frame: &mut Frame, area: Rect, app: &App) { // Show overall project progress as the average of all components. - let total: u32 = app.dashboard_items.iter().map(|c| c.completion as u32).sum(); + let total: u32 = app + .dashboard_items + .iter() + .map(|c| c.completion as u32) + .sum(); let avg = if app.dashboard_items.is_empty() { 0 } else { @@ -175,7 +179,11 @@ fn draw_component_detail(frame: &mut Frame, area: Rect, app: &App) { .bg(Color::DarkGray), ) .percent(avg as u16) - .label(format!("{}% across {} components", avg, app.dashboard_items.len())); + .label(format!( + "{}% across {} components", + avg, + app.dashboard_items.len() + )); // Stack the gauge with a summary paragraph below it. let vert = Layout::default() @@ -420,8 +428,6 @@ fn build_status_style(status: &BuildStatus) -> Style { .fg(Color::Yellow) .add_modifier(Modifier::BOLD), BuildStatus::Success => Style::default().fg(Color::Green), - BuildStatus::Failed(_) => Style::default() - .fg(Color::Red) - .add_modifier(Modifier::BOLD), + BuildStatus::Failed(_) => Style::default().fg(Color::Red).add_modifier(Modifier::BOLD), } } diff --git a/package.json b/package.json index c582dc03..87a5434d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@assetpack/core": "^1.4.0", + "@playwright/test": "^1.60.0", "@rescript/core": "^1.6.1", "rescript": "^12.2.0", "vite": "^6.2.0" diff --git a/src/app/screens/training/TrainingMenuScreen.res b/src/app/screens/training/TrainingMenuScreen.res index 880ccca5..feb11244 100644 --- a/src/app/screens/training/TrainingMenuScreen.res +++ b/src/app/screens/training/TrainingMenuScreen.res @@ -246,7 +246,7 @@ let make = (): Navigation.appScreen => { }) Container.on(backContainer, "pointertap", _ => { switch GetEngine.get() { - | Some(engine) => ScreenConstructors.navigateTo(ScreenConstructors.mainHub) + | Some(_engine) => ScreenConstructors.navigateTo(ScreenConstructors.mainHub) | None => () } })