Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/actions.lock
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ workflows:
'.github/workflows/ui-ci.yml':
- 'actions/cache@v6.1.0'
- 'actions/checkout@v7.0.1'
- 'ocaml/setup-ocaml@93303b622b2522e4411e295f9e77411a24912ac7'
dependencies:
'actions/cache@v6.1.0':
ref: 'v6.1.0'
Expand Down Expand Up @@ -98,6 +99,11 @@ dependencies:
commit: 'sha1-6037f33647c3f17758a2356c80fc4a53d7e0685d'
owner_id: 75048950
repo_id: 623796603
'ocaml/setup-ocaml@93303b622b2522e4411e295f9e77411a24912ac7':
ref: '93303b622b2522e4411e295f9e77411a24912ac7'
commit: 'sha1-93303b622b2522e4411e295f9e77411a24912ac7'
owner_id: 1841483
repo_id: 220799100
'hyperpolymath/deed-ecosystem@main':
ref: 'main'
commit: 'sha1-ed83d6927e8fb21431e403dbf6d7a4af96772746'
Expand Down
147 changes: 136 additions & 11 deletions .github/workflows/ui-ci.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# This workflow is managed by gh actions-lock.
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
# This workflow is managed by gh actions-lock.
# Product CI (UI/web): compiles the ReScript UI under Bun, builds the wasm
# core, runs the UI + contract tests, and produces the web bundle.
# Product CI (UI/wasm): compile-verify changed AffineScript sources, build the
# Rust wasm core, and run the live UI/contract test suites.
#
# The UI was ported from ReScript to AffineScript (PR #76). The AffineScript
# compiler verifies .affine files (`affinescript check`) but ships no web UI
# emitter yet, so there is deliberately no UI bundling here — see
# docs/decisions/ui-web-bundle-deferred-2026-09-22.adoc.
name: ui-ci

on:
Expand All @@ -13,16 +16,144 @@
workflow_dispatch:

permissions:
actions: read

Check warning on line 19 in .github/workflows/ui-ci.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this read permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_nexia-list&issues=AaDIzcn2NlnLfb4coIgE&open=AaDIzcn2NlnLfb4coIgE&pullRequest=102
contents: read

concurrency:
group: ui-ci-${{ github.ref }}
cancel-in-progress: true

# Compile-verifies changed `.affine` files with the canonical AffineScript
# compiler (hyperpolymath/affinescript), pinned to a commit SHA.
# Estate-standard split gate (standards#446 honest-gating):
# * ADDED .affine files -> BLOCKING (new code must compile)
# * MODIFIED legacy files -> advisory until the port backlog clears
# * Toolchain failures -> advisory skip (reported LOUDLY, never silent)
env:
LEGACY_BLOCKING: "false"
COMPILER_REPO: hyperpolymath/affinescript
COMPILER_REF: d2875a552f1d389b4a60c4adfdc02ae53e36aca3

jobs:
affinescript-verify:
name: AffineScript Verify
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v7.0.1
with:
fetch-depth: 0

- name: Determine changed .affine files
id: changed
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
else
BASE="${{ github.event.before }}"
fi
if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null \
|| printf '%s' "$BASE" | grep -qE '^0+$'; then
BASE="$(git rev-parse HEAD^ 2>/dev/null || git rev-parse HEAD)"
fi
ADDED="$(git diff --name-only --diff-filter=A "$BASE" HEAD -- '*.affine' || true)"
MODIFIED="$(git diff --name-only --diff-filter=CMR "$BASE" HEAD -- '*.affine' || true)"
FILES="$(printf '%s\n%s\n' "$ADDED" "$MODIFIED" | sed '/^$/d')"
if [ -z "$FILES" ]; then
echo "any=false" >> "$GITHUB_OUTPUT"
echo "No changed .affine files — nothing to verify."
else
echo "any=true" >> "$GITHUB_OUTPUT"
echo "Changed .affine files (added gate hard; modified advisory):"
echo "$FILES"
{ echo 'files<<EOF'; echo "$FILES"; echo 'EOF'; } >> "$GITHUB_OUTPUT"
{ echo 'added<<EOF'; echo "$ADDED"; echo 'EOF'; } >> "$GITHUB_OUTPUT"
fi

- name: Checkout AffineScript compiler
if: steps.changed.outputs.any == 'true'
continue-on-error: true
uses: actions/checkout@v7.0.1
with:
repository: ${{ env.COMPILER_REPO }}
ref: ${{ env.COMPILER_REF }}
path: .affinescript-compiler

- name: Set up OCaml
if: steps.changed.outputs.any == 'true'
continue-on-error: true
uses: ocaml/setup-ocaml@93303b622b2522e4411e295f9e77411a24912ac7 # v3
with:
ocaml-compiler: "5.1"

- name: Build compiler
id: build
if: steps.changed.outputs.any == 'true'
continue-on-error: true
working-directory: .affinescript-compiler
run: |
opam install . --deps-only
opam exec -- dune build
echo "ok=true" >> "$GITHUB_OUTPUT"

- name: Verify changed .affine files (added files gate; legacy advisory)
if: steps.changed.outputs.any == 'true'
run: |
set -u
if [ "${{ steps.build.outputs.ok }}" != "true" ] || [ ! -d .affinescript-compiler ]; then
echo "::warning::AffineScript toolchain unavailable (compiler checkout/opam/build failed) — verification SKIPPED, not passed."
{
echo "## AffineScript Verify"
echo "⚠️ **SKIPPED — toolchain unavailable.** The compiler did not build;"
echo "no \`.affine\` file was verified. This is an infra flake, not a pass."
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
cd .affinescript-compiler
is_added() {
case $'\n'"${ADDED_SET}"$'\n' in *$'\n'"$1"$'\n'*) return 0 ;; *) return 1 ;; esac
}
ADDED_SET="$(cat <<'OUTER'
${{ steps.changed.outputs.added }}
OUTER
)"
ADDED_SET="$(printf '%s\n' "$ADDED_SET" | sed 's/^[[:space:]]*//' | sed '/^$/d')"
added_fail=""
legacy_fail=""
while IFS= read -r f; do
[ -z "$f" ] && continue
abs="$GITHUB_WORKSPACE/$f"
echo "::group::check $f"
if opam exec -- dune exec affinescript -- check "$abs" 2>&1; then
echo "✅ $f"
elif is_added "$f"; then
echo "::error file=$f::AffineScript check failed (ADDED file — blocking)"
echo "❌ $f failed (added file — blocking)"
added_fail="$added_fail$f"$'\n'
else
echo "::warning file=$f::AffineScript check failed (legacy port — advisory until backlog clears)"
echo "⚠️ $f failed (legacy port — advisory)"
legacy_fail="$legacy_fail$f"$'\n'
fi
echo "::endgroup::"
done <<'OUTER'
${{ steps.changed.outputs.files }}
OUTER

{
echo "## AffineScript Verify (split gate)"
[ -n "$added_fail" ] && { echo "❌ Added files that MUST compile but did not:"; printf '%s' "$added_fail" | sed '/^$/d; s/^/- /'; }
[ -n "$legacy_fail" ] && { echo "⚠️ Legacy files failing (advisory until port backlog clears):"; printf '%s' "$legacy_fail" | sed '/^$/d; s/^/- /'; }
[ -z "$added_fail" ] && [ -z "$legacy_fail" ] && echo "✅ All changed .affine files compile."
} >> "$GITHUB_STEP_SUMMARY"

if [ -n "$added_fail" ]; then exit 1; fi

build-test:
name: rescript + wasm + bun test + bundle
name: wasm core + bun tests
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
Expand Down Expand Up @@ -53,17 +184,11 @@
- name: Install JS dependencies
run: bun install --frozen-lockfile

- name: Compile ReScript
run: bun run build:res

- name: Build wasm core
run: bun run build:wasm

- name: UI + contract tests
run: bun run test:ui

- name: Bundle web app
run: bun run build:web

- name: Lint
run: bun run lint
22 changes: 13 additions & 9 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ setup:
build-wasm:
bun run build:wasm

# Build the project (ReScript compile + Bun web bundle)
# Depends on build-wasm: the bundler resolves web/wasm/nexia_core.js.
# Build the project (= the wasm core). AffineScript UI web emit is pending
# upstream — docs/decisions/ui-web-bundle-deferred-2026-09-22.adoc.
build: build-wasm
bun run build
@echo "wasm core built into web/wasm/ (UI bundling deferred, see decision doc)"

# Run all tests (Rust core + UI)
# Depends on build: the UI tests import the generated *.res.js and the wasm
# bindings, so a clean checkout cannot go straight to `just test` without them.
# Both builds are incremental — a warm no-op costs well under a second.

# Run all tests (Rust core + UI/wasm contract)
# Depends on build: the four live bun suites import the wasm bindings from
# web/wasm/, so a clean checkout cannot go straight to `just test` without
# building first. Builds are incremental — a warm no-op is near-instant.
test: build
bun run test

Expand All @@ -37,9 +38,12 @@ test-rust:
ld-new name:
bun scripts/ld-mint.js {{name}}

# Run the development server (http://localhost:5173)
# Dev server for the web UI — RETIRED with the ReScript port. The AffineScript
# compiler has no web UI emitter yet; bundling resumes later (see
# docs/decisions/ui-web-bundle-deferred-2026-09-22.adoc). Until then the inner
# loop is `just build` -> `just test` (wasm core + contract suites).
run:
bun run dev
@echo "just run: no web UI dev server (AffineScript emit pending - see docs/decisions/ui-web-bundle-deferred-2026-09-22.adoc)" && exit 1

# Static checks — Biome, rustfmt, clippy
# Mirrors rust-ci.yml exactly: --all-targets --features wasm, run from the
Expand Down
2 changes: 1 addition & 1 deletion TEST-NEEDS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,5 @@ requirement
----
bun run test # cargo test (core) + bun test (UI/contract)
bun run test:rust # Rust only
bun run test:ui # UI/contract only (needs build:res + build:wasm first)
bun run test:ui # wasm/UI contract tests (needs build:wasm first)
----
6 changes: 3 additions & 3 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
"scripts/**/*.js",
"ui/tests/**/*.js",
"web/**/*.js",
"!ui/src/**/*.res.js",
"!ui/tests/**/*.res.js",
"!web/dist/**",
"!web/wasm/**"
]
Expand All @@ -17,6 +15,8 @@
},
"linter": {
"enabled": true,
"rules": {"recommended": true}
"rules": {
"recommended": true
}
}
}
23 changes: 0 additions & 23 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions docs/decisions/ui-web-bundle-deferred-2026-09-22.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
= Decision: UI web bundling deferred until AffineScript emits a web UI target
:author: Arena agent (fix PR)
:revdate: 2026-09-22
:status: Accepted

== Context

PR #76 ("Ported via Harvard Engine (Semantic pass)") rewrote the UI from ReScript
(`ui/src/*.res`) to AffineScript (`ui/src/*.affine`). The port moved the sources but
left the *tooling* behind pointing at the old pipeline:

* `bun run build:res` invoked `cd ui && bunx rescript`, but `ui/rescript.json` had
already been deleted — `ui-ci` failed on every run since the port
(`rescript.json: No such file or directory`, exit 2).
* `bun run build:web` bundled `ui/src/Main.res.js`, which no longer exists anywhere.
* Four `ui/tests/*.test.js` wrappers imported compiled test artefacts
(`*.res.js`) that can no longer be produced, so `bun test ui/tests` failed in
four of eight suites.
* `package.json` still carried the zombie dependencies `rescript`,
`@rescript/core`, `@rescript/react`, `react`, `react-dom`.

The AffineScript compiler (hyperpolymath/affinescript, pinned commit
`d2875a552f1d389b4a60c4adfdc02ae53e36aca3`) currently offers `affinescript check`
(compile verification / type-check). It does **not** yet emit a JavaScript/web UI
target, so a web bundle of the Elmish React UI cannot be produced from the
`.affine` sources today. The estate's own `affinescript-verify.yml` (standards)
anticipates this state with the split gate: added `.affine` files gate hard,
modified legacy ports are advisory until the port backlog clears.

== Decision

1. `ui-ci` adopts the **estate-canonical AffineScript Verify job** (split gate,
`COMPILER_REF` pinned by SHA) instead of the dead ReScript compile step.
2. **UI web bundling is deferred** until AffineScript ships a web UI emitter.
`build:web`, `build:res*`, the `dev` harness, `scripts/build.js` and
`scripts/dev.js` are removed. No part of the estate deploys a `web/dist`
bundle: `casket-pages.yml` builds the documentation site independently via its
Haskell SSG, and `web/dist` was never committed or published.
3. The four dead test wrappers are deleted. Behavioural runtime coverage is
carried by the wasm-contract suites (`agents`, `contract`, `exchange`,
`wikilink`), which import only the wasm core; compile-level coverage of the
ported modules (including `*Tests.affine`) is carried by the verify job.
4. Zombie dependencies are removed from `package.json`; `bun.lock` regenerated.

== Consequences

* `ui-ci` is green-honest again: every step either passes for real or reports a
labelled advisory — no silent successes, no permanently-red noise.
* Local `bun run dev` hot-reload of the UI disappears until the emitter lands;
wasm-core development flows through `bun run build:wasm` + `bun run test:ui`.
* When AffineScript gains a UI emitter, resurrect bundling from git history and
close this decision with a superseding ADR.
4 changes: 2 additions & 2 deletions docs/wikis/Developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ This is the discipline Nexia already proved with `Notebook::backlinks` — a rev
|---|---|---|
| Core engine | Rust — Note/Notebook, backlinks, wiki-links, substring search, JSON storage | [`core/`](https://github.com/hyperpolymath/nexia-list/tree/main/core) |
| **λδ substrate** | **Built** — reader, value model, evaluator + Budget, hygienic macros, multimethods, prelude, and the notebook host seam (~3,400 LOC excl. tests, ~64% of the core) | [`core/src/lambdadelta/`](https://github.com/hyperpolymath/nexia-list/tree/main/core/src/lambdadelta), `core/src/lambdadelta_host.rs` |
| Browser bridge | wasm-bindgen (**wired**) — the Rust core compiled to one WASM bundle; all 31 exports bound in ReScript | `core/src/wasm.rs`, [`ui/src/store/WasmStore.res`](https://github.com/hyperpolymath/nexia-list/blob/main/ui/src/store/WasmStore.res) |
| UI | ReScript 11, **hand-rolled** TEA (Model/Msg/Update/View) on `@rescript/react`, Bun bundler | [`ui/`](https://github.com/hyperpolymath/nexia-list/tree/main/ui) |
| Browser bridge | wasm-bindgen (**wired**) — the Rust core compiled to one WASM bundle; all exports consumed via the wasm store | `core/src/wasm.rs`, [`ui/src/store/WasmStore.affine`](https://github.com/hyperpolymath/nexia-list/blob/main/ui/src/store/WasmStore.res) |
| UI | **AffineScript** (`.affine`, ported from ReScript 11), hand-rolled TEA (Model/Msg/Update/View); compile-verified by `affinescript check` in CI — web emit pending (see `docs/decisions/ui-web-bundle-deferred-2026-09-22.adoc`) | [`ui/`](https://github.com/hyperpolymath/nexia-list/tree/main/ui) |
| Tooling | **Bun only** — packages, tasks, tests, bundler, and dev server | [`scripts/`](https://github.com/hyperpolymath/nexia-list/tree/main/scripts) |
| Persistence | Human-readable JSON via IndexedDB + file download/upload | browser |

Expand Down
2 changes: 1 addition & 1 deletion docs/wikis/Glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ A note that serves as a template: other notes inherit its [attributes](#attribut
Ambient recall: for the note you're looking at, the top related notes by TF-IDF cosine similarity over the [concordance](#concordance--inverted-index) — surfaced as a "Related notes" panel that appears only when non-empty. DEVONthink's crown jewel; the feature that makes the archive write back. Closes the *resurface* stage of the loop.

### TEA
The Elm Architecture — Model / Msg / Update / View with pure update functions and exhaustive pattern matching. Nexia-List's UI is a **hand-rolled** TEA loop in ReScript 11 on `@rescript/react` (the `rescript-tea` library was removed as unused). See [Developer](Developer).
The Elm Architecture — Model / Msg / Update / View with pure update functions and exhaustive pattern matching. Nexia-List's UI is a **hand-rolled** TEA loop, ported to **AffineScript** (`.affine`) and compile-verified in CI; web re-emit is pending upstream (see the 2026-09-22 decision under `docs/decisions/`). See [Developer](Developer).

### Tinderbox
The macOS spatial-hypertext tool that inspired Nexia-List — notes on a canvas, [agents](#agent), [prototypes](#prototype), attribute-driven emergence. Its ceiling is a bolted-on, non-extensible formula language and single-vendor, macOS-only mortality. Nexia-List carries its spirit cross-platform, open, and homoiconic to the core.
Expand Down
2 changes: 1 addition & 1 deletion docs/wikis/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Today's tools are strong at capture and link and effectively broken at **reason*
|---|---|---|
| Core engine | Rust (serde, uuid, chrono) | Note/Notebook model, backlinks reverse index, substring search, JSON storage |
| Browser bridge | wasm-bindgen (wired) | Compiles the Rust core to a single WASM bundle that *is* the engine, client-side; loaded at boot and covered by a CI contract test |
| UI | ReScript 11 + hand-rolled TEA on `@rescript/react`, Bun bundler | Model / Msg / Update / View; type-safe functional UI |
| UI | AffineScript + hand-rolled TEA (Model/Msg/Update/View), `affinescript check` in CI | Type-safe functional UI; web emit pending upstream |
| Substrate (built) | λδ (LambdaDelta) — homoiconic Lisp in the Rust core | Notebook-as-data; multimethods on `:type`/`:op`; sandboxed Budget; opt-in, invisible by default. The interpreter, macros, multimethods, prelude, and notebook host all exist in `core/src/lambdadelta/` |
| Desktop/mobile shell (optional) | [Gossamer](https://github.com/hyperpolymath/gossamer) — external sibling | Thin webview wrapping the identical web bundle; not built in this repo's CI |

Expand Down
Loading
Loading