168 lines
7.6 KiB
Markdown
168 lines
7.6 KiB
Markdown
# BrevyScribe
|
|
|
|
A fork of [FancyScribe](https://github.com/NilsUeter/fancyscribe) that prints
|
|
**generic datasheets** rather than a record of one particular army list.
|
|
|
|
FancyScribe renders a BattleScribe or New Recruit roster as 10th-edition-style
|
|
datacards, showing exactly the wargear you picked. That is the right thing for a
|
|
list you are about to play, but the wrong thing for a reference card you want to
|
|
keep: the official cards show *every* option a unit could take. BrevyScribe
|
|
rewrites the roster on the way in so the printed cards read like the official
|
|
ones.
|
|
|
|
Three transforms do the work, all of them toggleable in the UI:
|
|
|
|
| Transform | What it does |
|
|
| --- | --- |
|
|
| **Merge duplicates** | Mutually exclusive wargear forces you to take a datasheet twice - one Skatros with a radium jezzail, another with a transuranic arquebus. Copies of the same unit are folded into one card carrying every option. |
|
|
| **Drop Leader/Support** | Removes the attachment rules. Once the army is built they say nothing you need mid-game, and they are long enough to push the rules you *do* need off the card. |
|
|
| **Split choice abilities** | An ability like *Canticles of the Omnissiah* arrives as one blob of text. This splits it into the intro rule plus one titled row per option, which is how the datasheets print it. |
|
|
|
|
Everything still runs in the browser. There is no server component, no upload,
|
|
no account, and no analytics; rosters are held in `localStorage` and never leave
|
|
the machine.
|
|
|
|
## Running it
|
|
|
|
```sh
|
|
npm install
|
|
npm run dev # http://localhost:5173
|
|
npm test # the parity suite, see below
|
|
npm run build # static files into dist/
|
|
```
|
|
|
|
`npm run lint` reports pre-existing findings inherited from upstream (mostly
|
|
`a11y/useButtonType`), so it is deliberately not part of CI. Compare against
|
|
`git show upstream/main:<file>` before treating any of them as new.
|
|
|
|
## Deploying to scribe.luxick.de
|
|
|
|
The build output is **static files** - no application server, no socket, nothing
|
|
for nginx to proxy to. The app does all its work in the browser, which is why
|
|
upstream can live on GitHub Pages.
|
|
|
|
One-time setup on the server:
|
|
|
|
```sh
|
|
sudo mkdir -p /var/www/brevyscribe
|
|
sudo chown "$USER" /var/www/brevyscribe
|
|
sudo cp deploy/nginx-scribe.luxick.de.conf /etc/nginx/sites-available/scribe.luxick.de
|
|
sudo ln -s /etc/nginx/sites-available/scribe.luxick.de /etc/nginx/sites-enabled/
|
|
sudo certbot --nginx -d scribe.luxick.de
|
|
sudo nginx -t && sudo systemctl reload nginx
|
|
```
|
|
|
|
Then every deploy is one command from a checkout on your own machine - it runs
|
|
the tests, builds, and rsyncs `dist/` over. The server needs no Node.
|
|
|
|
```sh
|
|
./deploy/deploy.sh
|
|
```
|
|
|
|
`--delete` is deliberate: asset filenames are content-hashed, so without it old
|
|
bundles would pile up forever. There is no state on the server and nothing to
|
|
back up; every roster lives in the browser's `localStorage`.
|
|
|
|
`vite.config.js` sets `base: "/"`, because this is served at a domain root -
|
|
upstream sets `/fancyscribe` for GitHub Pages. If you ever serve it from a
|
|
subpath, that is the setting to change.
|
|
|
|
Note that `index.html` loads Noto Sans from Google Fonts, so a page load reaches
|
|
out to `fonts.googleapis.com`. If you would rather it did not, drop the
|
|
`<link>` tags and self-host the font next to the ones already in `public/fonts/`.
|
|
|
|
## How the transforms fit in
|
|
|
|
```
|
|
upload/.rosz -> unzip -> DOMParser -> applyTransforms -> Create40kRoster* -> render
|
|
^
|
|
src/transforms/index.js
|
|
```
|
|
|
|
`src/App.jsx` runs them between parsing the XML and building the roster, so
|
|
flipping a toggle rebuilds from the original XML with no re-upload. That is also
|
|
why saved rosters hold the raw roster XML rather than the parsed object.
|
|
|
|
Which abilities get stripped and split is configured in
|
|
[`src/transforms/config.js`](src/transforms/config.js) - add to
|
|
`abilitiesToStrip` and `abilitiesToConvert` as you meet more of them.
|
|
|
|
## The test suite
|
|
|
|
The transforms began as three standalone Python scripts (`merge_duplicate_units`,
|
|
`remove_leader_abilities`, `convert_choice_abilities`) that rewrote the `.ros`
|
|
file before it was uploaded to FancyScribe. They are gone now, but they are what
|
|
the tests measure against.
|
|
|
|
`src/transforms/__fixtures__/` holds four 11th-edition rosters plus, for each,
|
|
the output those scripts produced: `<name>.{merge,strip,convert,all}.ros`. The
|
|
suite runs each transform over the input and asserts it produces the same roster.
|
|
That is worth more than a snapshot of the current code, because the expected
|
|
output came from a **different implementation** that was verified by actually
|
|
printing the cards.
|
|
|
|
That property is easy to destroy and hard to notice: regenerate a fixture from
|
|
the JS and the test compares the code against itself, passing no matter what it
|
|
does. So `scripts/update-fixtures.mjs` will not overwrite an existing fixture
|
|
without `--force`. Use it when you add a new example roster, and read the diff:
|
|
|
|
```sh
|
|
# drop a new roster in src/transforms/__fixtures__/, then
|
|
npm run fixtures
|
|
```
|
|
|
|
Comparison is structural rather than textual. The scripts edited the file as text
|
|
to keep its formatting byte for byte; these transforms build DOM nodes, so
|
|
attribute order and whitespace legitimately differ. Generated `id`/`typeId`
|
|
values are compared as *tokens*, so what has to match is that ids are shared and
|
|
distinct in the same pattern, not that both implementations hash alike.
|
|
|
|
`integration.test.js` goes further and pushes the transformed document through
|
|
the real roster parser, checking what lands on the card: one Skitarii card
|
|
carrying both weapons, the data-tether ability travelling with the model that
|
|
brings it, no Support ability left, and each Canticle its own row.
|
|
|
|
One wrinkle: the tests run under jsdom, which does not implement scoped selectors
|
|
the way browsers do. `force.querySelectorAll("force>selections>…")` finds nothing
|
|
there, where a browser matches the selector against the whole tree and then keeps
|
|
the descendants of `force` - so under jsdom the parser found no units at all. The
|
|
two call sites that relied on it now say `:scope>…`, which means the same thing in
|
|
a browser and works in both.
|
|
|
|
## Staying current with upstream
|
|
|
|
FancyScribe is actively developed - 11th edition support landed recently - and
|
|
this fork touches little of it. `upstream` is wired up:
|
|
|
|
```sh
|
|
git fetch upstream
|
|
git merge upstream/main
|
|
```
|
|
|
|
Conflicts should be confined to `src/App.jsx`, `index.html`, `vite.config.js` and
|
|
`package.json`. `src/transforms/` is entirely new and will never conflict.
|
|
|
|
The fork also hides three things upstream shows, because a generic datasheet has
|
|
nothing to say with them: the roster overview card and its charts, the unit
|
|
composition, and the per-unit points cost. Those decisions live in
|
|
[`src/fork.js`](src/fork.js) - another file upstream does not have - and
|
|
`src/10th/Roster.jsx` reaches for them on as few lines as possible:
|
|
|
|
- it imports `ShortSummaryTable` from `../fork` instead of `./ShortSummaryTable`,
|
|
a one-line change that leaves the render site untouched. The upstream
|
|
component is still in the tree, unused, so its future diffs keep applying.
|
|
- `hideModelCount` is pinned to `HIDE_UNIT_COMPOSITION` instead of being a piece
|
|
of checkbox state.
|
|
|
|
Only the two checkboxes and the `pts` span are deleted outright, so a merge that
|
|
touches them will say so rather than quietly bringing them back.
|
|
|
|
## Credit
|
|
|
|
All the hard parts - the parsing, the card layout, the print CSS - are
|
|
[Nils Ueter's](https://github.com/NilsUeter/fancyscribe), with parsing logic
|
|
descended in turn from
|
|
[PrettyScribe](https://github.com/rweyrauch/PrettyScribe). Upstream ships no
|
|
licence file, so treat this fork as a private, personal deployment rather than
|
|
something to redistribute.
|