Skip to content

Forms #875 - #1281

Open
Polleps wants to merge 65 commits into
developfrom
forms-#875
Open

Forms #875#1281
Polleps wants to merge 65 commits into
developfrom
forms-#875

Conversation

@Polleps

@Polleps Polleps commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Related Issues

closes #875

Checklist

  • Add changelog entry linking to issue, describe API changes
  • Add or update tests if needed
  • Update docs if needed

@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@Polleps
Polleps force-pushed the forms-#875 branch 2 times, most recently from 040d35f to 03b037f Compare August 20, 2026 09:50
Polleps and others added 26 commits September 2, 2026 11:45
The .po catalogs and pnpm-lock.yaml are generated artifacts; both were
resolved to develop's side during the rebase, so the forms strings and the
form-app/form-renderer workspace entries were missing. Re-extracted with
wuchale (no AI translation run) and re-locked with pnpm install.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rights walk now consults the per-query verdict memo by subject
before fetching an ancestor, and loads ancestors from materialized
propvals instead of decoding their Loro snapshots. Query loops stop
calling resolve_query_member after 16 consecutive denials while filling
a page, so a fully-private listing no longer fetches every match.

Co-authored-by: Polle Pas <polleps@gmail.com>
Each member can carry its own read grant, so a streak of private
siblings must not skip later readable rows. Keep resolving until the
page is full of authorized hits; the ancestor-fetch memo is what makes
that cheap.

Co-authored-by: Polle Pas <polleps@gmail.com>
FormCondition resources on pages, fields, and layout blocks, evaluated
in document order (AND; empty = always visible). Shared fixtures keep
the TS renderer and Rust submit validator in lockstep. Hidden required
fields are not errors; submitted values for them are dropped.

Co-authored-by: Polle Pas <polleps@gmail.com>
Co-authored-by: Polle Pas <polleps@gmail.com>
The compiler emitted a broken _w_ctx reference, which crashed the form
builder as soon as the page settings pane mounted the editor.

Co-authored-by: Polle Pas <polleps@gmail.com>
Wuchale rewrote "<FaPlus /> Add condition" into W_tx_ with an undefined
_w_ctx_, which crashed the form builder as soon as the page pane mounted.
Wrap the label in a span so it extracts as a plain string.

Co-authored-by: Polle Pas <polleps@gmail.com>
Polleps and others added 22 commits September 2, 2026 11:46
The e2e server script resolved its binary as `$REPO_ROOT/target/debug/
atomic-server`. That is only where cargo puts it when nothing has moved the
target dir — `CARGO_TARGET_DIR` or a `build.target-dir` in any cargo config
(commonly `~/.cargo/config.toml`, to share one target dir across checkouts or
park it on a faster disk) sends it elsewhere, and the repo-local path then
still holds a binary from before that config existed. The suite started
happily and served stale server code, failing specs for behaviour fixed weeks
earlier, while `cargo build` reported success and changed nothing under test.

Ask cargo instead of guessing, and keep the conventional path as the fallback
for when cargo cannot answer.

Two more things this turned up:

- Both staleness guards counted the wuchale-generated locale catalogs as
  sources. Wuchale rewrites them byte-for-byte identically on every vite
  start, so the guards fired for anyone who had run `pnpm start` and taught
  you to pass `--stale-ok` reflexively — which is the flag that lets a
  genuinely stale build through. Exclude the generated files so the guard
  only fires when it means something.

- `search.spec.ts`'s tag test named its tags `first-tag` / `second-tag`. The
  folder name was already unique per run, but the tags were not, and `tag:`
  search is a Tantivy query over the whole store — so every earlier run left
  another folder carrying those tags. Once enough runs had accumulated,
  `tag:first-tag` matched dozens of old folders, this run's fell outside the
  rendered result list, and the spec timed out looking for it. It only
  reproduces on a store with history, so it survives being run in isolation.

Also guard `forms.spec.ts`'s sync predicate with `window.store?.`, matching
the equivalent poll in `e2e.spec.ts`: a `waitForFunction` predicate that
throws fails the wait outright instead of polling again, so a frame where the
store has not attached yet surfaced as `Cannot read properties of undefined`.

Verified on a store carrying history: the tag spec goes from failing to
passing 5/5, and a full run is 176 passed / 1 failed, the remaining failure
being a chatroom load flake that passes 3/3 in isolation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wuchale output from running the dev server: fills in the de/es/fr
translations for the Cloud Vault, device-link and getting-started strings,
and refreshes one placeholder comment in the AI tool-part message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`CLAUDE.local.md` / `AGENTS.local.md` are the per-checkout, uncommitted
counterparts to the tracked `CLAUDE.md` / `AGENTS.md`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rebase left the four catalogs disagreeing with each other: strings
develop had retired (the personal -> private drive rename, `Open by URL`)
came back as active entries in de/es/fr because the branch's translation
commits reintroduced them, and the new entries from both sides landed in
a different order per language.

Rebuild de/es/fr against en's structure — same msgid sequence, same
reference comments — and demote the retired strings to `#~` entries with
their translations kept, so nothing translated is thrown away. A
data-browser build no longer rewrites any of the four.
Pre-existing on the branch, not caused by the rebase, but they are what
stands between it and a green pipeline.
`create a form, add every field type, and persist across reload` did two
unrelated jobs. Adding one field of all 24 types cost ~20s of its ~44s —
each add is about five sequential resource saves (property, ontology, row
class, field, page) — and the regression walk it was welded to only ever
touched four of those fields.

Split in two. The breadth test adds all 24 and reloads; the walk adds the
four it edits and keeps its sequence (creation -> property sync -> rename
-> delete -> reload) intact, which is what its comment says is the point.
Coverage is unchanged.

Measured on forms.spec.ts at 2 workers, alternating before/after runs to
cancel machine drift: 43.5/43.7s before, 34.3/34.8s after. The walk alone
drops from 44.5s to 15.3s, which is the one you re-run while debugging.

`test.slow()` stays on the breadth test — 33s here, and CI boxes are
slower. The walk no longer needs it.
Polleps and others added 7 commits September 2, 2026 15:30
Adds `form-open-at` / `form-close-at` to the forms ontology and gates every
visitor-facing `/form/{id}` route on the resulting window: a published form
is not yet open before `form-open-at` and closed from `form-close-at`, each
with its own 410 wording (and the moment in UTC). The builder's Settings tab
gets a Schedule section, and the Publish toggle shows a Scheduled / Closed
badge so "Unpublish" is not the only signal on a form no visitor can open.

Covered by the `forms::` scheduling unit tests, `form_submission_flow`
step 7b, and the "a scheduled window opens and closes a published form" e2e.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…m routes

Two unrelated defects behind "once a commit for a form is parked, the
server's copy never advances again".

The parked commit: class extenders shape a GET response with
`Resource::set`, which also records a Loro op on the doc decoded from the
stored snapshot, and JSON-AD serialization re-exported that doc as the
served `loroUpdate`. The Form extender did this for `form-submission-summary`
on every HTTP GET, so a builder tab hydrated over HTTP held an op the server
never persisted and its next delta, whatever the edit, depended on it.
`get_resource_extended` now pins the served `loroUpdate` to the persisted
snapshot after any extender (and the `incomplete` marker), and the Form
extender attaches the summary without touching the doc.

"Never advances again": the server had converged (the client's snapshot
recovery works), but `/form/{id}/definition` answered `410` without
`Cache-Control`, and Chromium replayed the cached `410` for every later
fetch of that URL — from the e2e probe and a console `fetch` alike. Every
form API response now carries `Cache-Control: no-store`, the e2e probe
fetches with `cache: 'no-store'`, and the trimmed reopen assertion in the
scheduled-window e2e is restored.

Regression tests: `class_extender::extended_get_serves_the_persisted_snapshot`
and `form_submission_flow` steps 1, 2 and 2b.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Forms / surveys - submit-only view

2 participants