diff --git a/.github/workflows/electron-build.yml b/.github/workflows/electron-build.yml index 8e824c5..6fa4862 100644 --- a/.github/workflows/electron-build.yml +++ b/.github/workflows/electron-build.yml @@ -38,6 +38,14 @@ jobs: with: fetch-depth: 0 # --generate-notes needs history to diff the tag against + # Before anything is built: the tag has to name the version the repository + # states, in package.json and in the citations. `npm version x.y.z` makes + # the commit and the tag together, so this only fails for a tag made by + # hand -- the way the tags once reached v3.1.3 while the repository, the + # website's About and the README's citation all still said 2.9.0. + - name: Check the tag matches the repository's version + run: node scripts/sync-version.mjs --check --expect "${GITHUB_REF_NAME#v}" + - env: GH_TOKEN: ${{ github.token }} run: | @@ -79,13 +87,6 @@ jobs: - run: npm ci - # Keeps installer filenames (…Setup 1.2.3.exe) in step with the tag being - # released, without needing a version-bump commit before every tag. - - name: Sync package version to tag - if: startsWith(github.ref, 'refs/tags/v') - shell: bash - run: npm version --no-git-tag-version --allow-same-version "${GITHUB_REF_NAME#v}" - - name: Build web bundle run: npm run build diff --git a/CITATION.cff b/CITATION.cff index 84aabc7..89ee346 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -11,7 +11,7 @@ authors: - family-names: Chaubey given-names: Shreyan alias: thethinkmachine -version: 2.9.0 +version: 3.1.3 license: LicenseRef-PolyForm-Noncommercial-1.0.0 repository-code: "https://github.com/thethinkmachine/AutomataStudio" url: "https://thethinkmachine.github.io/AutomataStudio/" diff --git a/CLAUDE.md b/CLAUDE.md index ce92c0f..3173f57 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,8 @@ npm run cli -- --help # the `automata` command line, from source (bin: cli/ npm run cli:build # vite build --config vite.cli.config.js -> dist-cli/ (runs under plain node) npm run cli:docs # docs/cli-reference.md and the guide's output blocks, from the real commands npm run media:cli # re-film the guide's terminal clips from docs/media/tapes/ (-- for one) +npm version 3.2.0 # release: bump, sync the citations, commit and tag v3.2.0 in one step +git push --follow-tags # ... and push the commit with its tag, which builds the release ``` `automata` is the app's engine from a terminal — run, test, trace, lint, compare, convert and grade machines; HOA/BA/JFLAP formats; learning; an MCP server; and Turing-machine halting proofs. `electron:build` builds `dist-cli/` too, and the installed app ships it with launchers in `resources/cli/`. See [The command line](.claude/skills/cli/SKILL.md). @@ -32,6 +34,8 @@ npm run media:cli # re-film the guide's terminal clips from docs/media/ `npm run bench` is also run by hand and never gates CI: timings move with the machine, so a build that failed on them would fail at random. Run it before and after a change to the engine, the player, the canvas or the space-time diagram; it marks a case only when it moved by more than its own noise, and marks a changed verdict whatever the timing did. See [The benchmark](.claude/skills/perf/SKILL.md). +**A release is `npm version`, never a hand-made tag.** package.json is the version's one source: the website's About and `automata --version` read it at build time, and CITATION.cff and the README's two citations repeat it as text. `npm version` runs [scripts/sync-version.mjs](scripts/sync-version.mjs) as its `version` hook, so the bump, those copies and the tag land in one commit; [tests/version.test.js](tests/version.test.js) fails when they disagree, and the release workflow refuses a tag that does not match package.json before it builds anything. Tags used to be the only place the version lived — the workflow rewrote package.json on the runner and never committed it — and they reached v3.1.3 while the website and the citation still said 2.9.0. + CI: `.github/workflows/deploy.yml` publishes `dist/` to GitHub Pages on push to `main`. `.github/workflows/electron-build.yml` packages win/mac/linux installers on every push and publishes a GitHub Release for `v*` tags. `.github/workflows/library-rebuild.yml` runs the tests and then starts the machine library's Publish workflow when `main` changes anything the library build reads (`js/`, `scripts/library/`) — the library is built by this repo's `main`, but its own workflows only run on its own events. It needs a `LIBRARY_DISPATCH_TOKEN` secret, and says so rather than failing when there is none. The package is `"type": "module"`. The two Electron entry points are CommonJS and carry a `.cjs` extension for that reason ([electron/main.cjs](electron/main.cjs), [electron/preload.cjs](electron/preload.cjs)). @@ -343,4 +347,3 @@ It also **records `document` listeners with their phase**, and `dispatchDocument - `js/examples/*.json` are fetched at runtime by name, so they are copied verbatim into `dist/` by a small plugin in [vite.config.js](vite.config.js) rather than hashed as bundler assets. - The build no longer runs `javascript-obfuscator`. It roughly doubled the bundle and cost ~26% time-to-interactive on a project whose source is public; the desktop build already skipped it. Re-add it as a Vite plugin if that trade is wanted back. -- `exportOpenSamples` in `export-ui.js` has no callers. diff --git a/README.md b/README.md index e9d4daf..a0ca3e6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # AutomataStudio -**An IDE for designing, simulating and analysing automata.** +**An IDE for designing, simulating and analysing automata and formal languages.** Draw a machine, run it step by step, derive its language, put it through the textbook constructions, and take it out as a diagram, LaTeX or working code. It covers 30 @@ -335,14 +335,14 @@ please cite it. GitHub's **"Cite this repository"** button (from author = {Chaubey, Shreyan}, title = {{AutomataStudio}: An {IDE} for Designing, Simulating and Analysing Automata}, year = {2026}, - version = {2.9.0}, + version = {3.1.3}, url = {https://github.com/thethinkmachine/AutomataStudio}, note = {Software} } ``` > Chaubey, S. (2026). *AutomataStudio: An IDE for designing, simulating and analysing -> automata* (Version 2.9.0) [Computer software]. +> automata* (Version 3.1.3) [Computer software]. > https://github.com/thethinkmachine/AutomataStudio Cite the version you actually used, so that others can reproduce your results. If you diff --git a/css/layout.css b/css/layout.css index a920d12..8f0a78b 100644 --- a/css/layout.css +++ b/css/layout.css @@ -199,12 +199,29 @@ body:has(.rpanel.is-empty) .hdr-right { centre. Centring both puts the mark's middle within 0.5px of the wordmark's cap-height middle, which is the alignment the eye actually reads. */ .logo-wrapper { + /* A button only so it is focusable and opens About; it must not look like + one. `font` comes first because the shorthand resets line-height, and + the stack below is measured at line-height 1. The padding gives the + focus ring room around the revealed byline, and the negative margin hands + the width back so the lockup sits exactly where the bare div used to. */ + font: inherit; display: inline-flex; align-items: center; gap: 6px; line-height: 1; - cursor: default; user-select: none; + padding: 6px 8px; + margin: 0 -8px; + border: 0; + border-radius: 8px; + background: none; + color: inherit; + cursor: pointer; +} + +.logo-wrapper:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 0; } /* The in index.html pulls in #logo-mark, whose strokes are `currentColor` @@ -229,7 +246,7 @@ body:has(.rpanel.is-empty) .hdr-right { /* margin: 0 because the About dialog's copy of this lockup is the

that names the dialog — it has no .modal-title to carry that job — and a UA heading margin there would push the byline off the wordmark. The header's - copy is a div and never had one to reset. */ + copy is a span and never had one to reset. */ .logo { font-family: var(--sans); font-size: 1.15rem; @@ -247,14 +264,81 @@ body:has(.rpanel.is-empty) .hdr-right { } .logo-author { - font-family: var(--mono); - font-size: 0.6rem; + font-family: var(--sans); + font-size: 0.68rem; + font-weight: 500; color: var(--text3); white-space: nowrap; - letter-spacing: 0.04em; + letter-spacing: 0.01em; padding-left: 2px; } +/* ─── The byline reveal ─── + Hover or focus turns the lockup into the About dialog's header: wordmark + over byline. Only transforms and opacity move, so nothing reflows — the + byline hangs below the wordmark out of flow, adding no width or height, and + the whole stack lifts 6px so the pair centres on the mark the way the + single wordmark did. 6px is half of the byline's 2px gap plus its 0.68rem + line, rounded. + + The enter waits 60ms and the leave does not: a pointer sweeping across the + header (or towards the window controls in the desktop app) should not + flicker the name on its way past. Hover is gated on `(hover: hover)` + because a touch browser keeps :hover stuck on after a tap, which would + leave the byline showing behind the About dialog it just opened. */ +.logo-stack { + position: relative; + display: flex; + flex-direction: column; + transition: transform .18s cubic-bezier(.2, .7, .2, 1); +} + +.logo-byline { + position: absolute; + left: 0; + top: calc(100% + 2px); + padding-left: 0; + opacity: 0; + transform: translateY(3px); + transition: opacity .14s ease-out, transform .18s cubic-bezier(.2, .7, .2, 1); + pointer-events: none; +} + +.logo-wrapper:focus-visible .logo-stack { + transform: translateY(-6px); +} + +.logo-wrapper:focus-visible .logo-byline { + opacity: 1; + transform: none; +} + +@media (hover: hover) { + .logo-wrapper:hover .logo-stack { + transform: translateY(-6px); + transition-delay: 60ms; + } + + .logo-wrapper:hover .logo-byline { + opacity: 1; + transform: none; + transition-delay: 60ms; + } +} + +/* Reduced motion keeps the fade and drops the movement: the stack still has + to make room, so it jumps there instead of travelling. */ +@media (prefers-reduced-motion: reduce) { + .logo-stack { + transition: none; + } + + .logo-byline { + transform: none; + transition: opacity .14s ease-out; + } +} + /* ─── HEADER MENU (More, and the Explore group inside it) ─── Anchored to its trigger rather than the viewport, so it overrides the position:fixed that .ctx uses for cursor-placed context menus. The .ctx- diff --git a/css/mobile.css b/css/mobile.css index 35bf8c8..6a2735b 100644 --- a/css/mobile.css +++ b/css/mobile.css @@ -1475,9 +1475,11 @@ /* Below this the wordmark is 100px of a 390px header spent saying something the mark already says. The mark stays — it is the logo, and an app with no - mark anywhere reads as a page that failed to finish loading. */ + mark anywhere reads as a page that failed to finish loading. The byline + goes with it, since it hangs off the wordmark; the mark alone still opens + About, where the credit is. */ @media (max-width: 520px) { - .hdr-left .logo { + .hdr-left .logo-stack { display: none; } diff --git a/electron/main.cjs b/electron/main.cjs index d5982bc..904cd72 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -670,7 +670,9 @@ function buildMenu() { ...(isMac ? [{ label: app.name, submenu: [ - { role: 'about' }, + // The app's own About, not Electron's stock panel: the Help menu opens + // this one too, and the stock panel has no author, licence or blurb. + { label: 'About AutomataStudio', click: () => sendMenuAction('about') }, { type: 'separator' }, { label: "Install 'automata' Command in PATH", click: () => installCliCommand() }, { label: "Uninstall 'automata' Command from PATH", click: () => uninstallCliCommand() }, diff --git a/index.html b/index.html index c6015b6..14ccd35 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@ gtag('config', 'G-R1LKJ6JFEF'); AutomataStudio +
-
+ +
+ +
@@ -2427,8 +2437,7 @@
Shreyan Chaubey
- +