Skip to content

A page for trying VisualText's analyzers, without an account - #22

Merged
ddehilster merged 2 commits into
mainfrom
feat/try-page
Sep 18, 2026
Merged

ddehilster merged 2 commits into
mainfrom
feat/try-page

Conversation

@ddehilster

Copy link
Copy Markdown
Member

/studio/try/ is a second page in the same build: pick one of VisualText's own analyzers,
type text, press Run, and see the pass sequence, the knowledge base, what the run wrote,
the parse trees, and — with Log files on — each pass's own tree and the text marked with
what its rules matched. It is drawn with @visualtext/analyzer-views and loads no Monaco:
5.5 kB of JavaScript.

Full write-up: docs/TRY-PAGE.md.

The decision this rests on: canned analyzers, not a sandbox

Running an analyzer is running its author's code, and docs/ARCHITECTURE.md says the
per-run sandbox strangers would need is not built. Of the three ways to have a public try
page — build the sandbox, ship only our own analyzers, or keep the invite gate — this is the
second.

The visitor never supplies code. The analyzers are copied in when the image is built,
from a pinned VisualText/analyzers release — the same analyzers.zip VisualText downloads
as its examples, so the page offers exactly the set a VisualText user has — and the page
offers no way to edit the NLP++. The text is data the rules read, not rules the engine runs.

Two things have to stay true for that argument to hold, and neither is enforced by the
server
, so both are written down rather than assumed:

  1. The page stays read-only. Accepting a pasted grammar, an uploaded .nlp, or an
    analyzer named by URL puts it straight back behind the sandbox that does not exist.
  2. /api/run is still the general endpoint — it accepts any files a caller sends. The
    restraint lives in the page, not the server. Making this reachable anonymously without
    rate-limiting and fronting /api/run would put an RCE endpoint on the host that serves
    visualtext.org.

This PR changes nothing about who can reach anything. The page ships behind whatever
already guards /studio/. INSTALL.md gains a section on what opening it wider would need.

Which analyzers, and how they were chosen

All 21 analyzers in the repository were run through POST /api/run with develop: true
before any UI existed. 19 ran clean. The eight on the page were picked from those for showing
different things; the survey table is in docs/TRY-PAGE.md so nobody repeats it.

Two that are not there:

  • parse-en-us is refused — it calls interactive(), which nlp_run.BLOCKED lists with
    the desktop app's popups. At both of its call sites it is a read-only predicate, and in both
    the false branch is the safer one (no openfile, an in-memory buffer). Unblocking it
    would very likely give the page the full English parser. Deliberately not done here —
    loosening a security control belongs in its own change, with someone deciding it on purpose.
    Written up as an open question.
  • tutorial-01 is 26 MB, over the run server's 8 MB limit, for a sequence of one pass.

Verification

  • All eight run clean against the shipped file sets (not the source tree): 0 problems,
    0 skipped trees.
  • passes() against the per-pass trees: 0 orphans across all eight. Both numbering traps
    are live in the set — corporate has a stub (takes no number), and a switched-off /nlp
    pass (keeps its number, writes no tree) is covered by the tests.
  • ruleMatches() over real trees fetched through /api/run/tree, on every pass of every
    analyzer. The all-@CODE passes correctly mark nothing.
  • docker build + smoke-test.sh pass under the restrictions the container runs with,
    including the new try-page checks.
  • 39 unit tests (6 new).

Writing src/try/result.ts found two real bugs: readOutput() returns a { output } wrapper
while <nlp-values> wants the parsed value (every field would have been listed as
output.something), and the run server calls a tree's size size where the views want
bytes. Both now live in one place with tests pinning them.

Not verified: the rendered page. There is no browser on the machine this was built on, so
npm run selftest could not run and nobody has watched it draw. Everything behind the pixels
is verified programmatically. Worth a look in a browser before merge.

Notes for the reviewer

  • analyzer-views/README.md says the package serves "both pages" — with this merged that is
    three. Left alone here since it was just rewritten; flagging for whoever owns it.
  • VisualText/analyzers has no LICENSE file. This page redistributes its contents to
    every visitor. .gitignore declines to commit the copy, for the same reason it declines to
    commit analyzer-templates, but the underlying question is the owner's.
  • Two small shared extractions to avoid drift, both pure moves: scripts/analyzer-files.mjs
    (what travels with an analyzer, shared with copy-samples.mjs) and src/theme.css (the
    palette, shared with the editor page).

🤖 Generated with Claude Code

ddehilster and others added 2 commits September 17, 2026 17:37
/studio/try/ is a second page in the same build: pick one of VisualText's
own analyzers, type text, press Run, and see the pass sequence, the
knowledge base, what the run wrote, the parse trees, and -- with Log files
on -- each pass's own tree and the text marked with what its rules matched.
It is drawn with @visualtext/analyzer-views and loads no Monaco (5.5 kB).

It is read-only, and that is the point. Running an analyzer is running its
author's code, and docs/ARCHITECTURE.md says the per-run sandbox strangers
would need is not built. This page sidesteps that rather than solving it:
the analyzers are copied in when the image is built, from a pinned
VisualText/analyzers release -- the same analyzers.zip VisualText downloads
as its examples -- and the only thing a visitor supplies is the text. The
visitor never supplies code.

Two things have to stay true for that, and neither is enforced by the
server: the page stays read-only, and /api/run -- which still accepts any
files a caller sends -- is not reachable anonymously without rate limits in
front. Both are written down rather than assumed.

Eight of the twenty analyzers in the bundle, each showing something the
others do not, 259 files and 1.45 MB in all. All twenty-one in the
repository were run through /api/run first; the survey, and why the rest are
left out, is in docs/TRY-PAGE.md so nobody repeats it. parse-en-us is
refused because it calls interactive(), which nlp_run.BLOCKED lists with the
desktop app's popups although at both its call sites it only reads state --
left alone here, and written up as an open question.

Also:

- scripts/analyzer-files.mjs holds what travels with an analyzer, shared
  with copy-samples.mjs so the two cannot drift on what an analyzer is.
- src/theme.css holds the palette and plain controls, shared with the
  editor page so a colour is changed once.
- src/try/result.ts keeps the two translations that are easy to get quietly
  wrong -- the run server's `size` against the views' `bytes`, and
  readOutput()'s { output } wrapper against what <nlp-values> takes -- in
  one place, with tests.
- smoke-test.sh proves the try page and its analyzers are in the image, and
  that one of them runs in debug mode, keeps a tree after every pass and can
  have one fetched back.
- update.sh tracks the analyzers release as it tracks visualtext-files, so a
  new one makes the app out of date.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The NLPFix analyzers carry a syntax-highlighted rendering of every pass and
dictionary beside it -- spec/dates.nlp.html next to spec/dates.nlp -- made
for a web page. Nothing here reads them: the sequence comes from
analyzer.seq and the knowledge base lists only .dict and .kbb, so they were
never listed, never opened and never read by the engine. They were still
fetched by every visitor and sent up again in the body of every run.

They were 46% of the try page's analyzers by size. Without them the catalog
is 188 files and 0.78 MB, down from 259 and 1.45 MB; Entities halves and
Dates and Times and Formatting lose two thirds each. All eight still run to
the same output, the same problems and the same trees.

The rule goes in analyzer-files.mjs, with the rest of what an analyzer is
made of, rather than in the try page's script alone: a rendering of a pass
is not part of an analyzer whichever page is showing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ddehilster
ddehilster merged commit a25cada into main Sep 18, 2026
2 checks passed
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.

1 participant