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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
# Builds the example Payload site, starts it with a throwaway in-memory database on the runner, and checks it
# over HTTP with sign-in cookies. About a minute.
- name: Example site smoke test
run: pnpm --filter @captiveorigins/onetaxonomy-payload-example smoke
run: pnpm --filter @extantape/onetaxonomy-payload-example smoke
# The OneOrigin brand copy (brand/oneorigin/) must match brand/oneorigin.lock, and the CSS built from it must be current.
# The source is OnePhylogeny's docs/oneorigin/; the check warns, without failing, when it has moved ahead of the copy.
- name: OneOrigin brand check
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Release: pushing a version tag (vX.Y.Z) builds, tests and *stages* every OneTaxonomy package on npm.
# Nothing goes public until the maintainer npm account approves each with 2FA
# Nothing goes public until the mdgilardi npm account, which owns the @extantape scope, approves each with 2FA
# (npmjs.com → your avatar → Staged Packages → Approve, or `npm stage approve <id>`).
# No token is stored: npm trusted publishing (OIDC) lets only this workflow stage.
name: Release
Expand Down Expand Up @@ -27,14 +27,14 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
test "$(gh api repos/${{ github.repository }} -q .description)" = "$(node -p "require('./package.json').description")" \
|| { echo "::error::GitHub's description differs from package.json: sync it (npm run packages:sync in captive-origins) and re-tag."; exit 1; }
|| { echo "::error::GitHub's description differs from package.json: copy the description to GitHub and re-tag."; exit 1; }
- name: Tag matches every package
run: for p in packages/*/; do test "${GITHUB_REF_NAME#v}" = "$(node -p "require('./${p}package.json').version")"; done
- run: pnpm build && pnpm typecheck && pnpm test
- name: Stage on npm
run: |
VERSION=$(node -p "require('./packages/core/package.json').version")
# payload (@captiveorigins/payload-plugin-onetaxonomy) isn't staged: its first npm version is published by hand
# payload (@extantape/payload-plugin-onetaxonomy) isn't staged: its first npm version is published by hand
# by the owner, and trusted publishing is set up after that. Add it here then.
for p in $(ls packages | grep -v -x payload); do
NAME=$(node -p "require('./packages/$p/package.json').name")
Expand Down
24 changes: 13 additions & 11 deletions CHANGELOG.md

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

A source-available framework for importing, curating, browsing and publishing **scientifically faithful** taxonomic classifications and phylogenetic trees: for developers, and for teachers, museums, researchers and animal keepers who don't write code.

Part of the [OneOrigin suite](https://github.com/CaptiveOrigins/onephylogeny/blob/main/docs/oneorigin/README.md): independent tools, with no subscriptions, that work alone or together.
Part of the [OneOrigin suite](https://github.com/ExtantApe/OnePhylogeny/blob/main/docs/oneorigin/README.md): independent tools, with no subscriptions, that work alone or together.

> **Status: early (Phase 1, milestone 4 of 8).** The pure TypeScript core, the importers (Newick, NEXUS, phyloXML, CSV/TSV checklists, Darwin Core Archives, OneTaxonomy JSON) and curated selective imports with coverage reports, views with undo/redo, layouts and the React explorer work and are tested (see `apps/playground`). A first Mac app (`apps/studio`) opens these files and saves views. Profiles and the Studio's project files are planned, and the Payload plugin has its first four parts (snapshots, views, import jobs, proposals and a small read API, tested on an in-memory database) and a small example site (`apps/payload-example`); see [the phase plan](docs/architecture/phase-plan.md). The five `@captiveorigins/onetaxonomy-*` packages are on npm as alpha versions; the Payload plugin isn't yet.
> **Status: early (Phase 1, milestone 4 of 8).** The pure TypeScript core, the importers (Newick, NEXUS, phyloXML, CSV/TSV checklists, Darwin Core Archives, OneTaxonomy JSON) and curated selective imports with coverage reports, views with undo/redo, layouts and the React explorer work and are tested (see `apps/playground`). A first Mac app (`apps/studio`) opens these files and saves views. Profiles and the Studio's project files are planned, and the Payload plugin has its first four parts (snapshots, views, import jobs, proposals and a small read API, tested on an in-memory database) and a small example site (`apps/payload-example`); see [the phase plan](docs/architecture/phase-plan.md). The five `onetaxonomy-*` packages are on npm as alpha versions, under `@extantape` from 0.1.0-alpha.4; the Payload plugin isn't yet.

## Principles

Expand All @@ -20,12 +20,12 @@ The full rules are in [scientific invariants](docs/architecture/scientific-invar

| Package | What it does | Status |
| --- | --- | --- |
| [`@captiveorigins/onetaxonomy-core`](packages/core) | Records, validation, bounded traversal, MRCA, tri-state monophyly, curated selections, coverage reports, snapshot checksums and diffs, views (actions, undo/redo, filters, highlights, projection), JSON Schema. No dependencies; runs anywhere. | Working |
| [`@captiveorigins/onetaxonomy-import`](packages/import) | Source adapters (CSV/TSV, Darwin Core Archive, JSON, Newick, NEXUS, phyloXML), reconciliation, selective imports, community presets | Working |
| [`@captiveorigins/onetaxonomy-layout`](packages/layout) | Rectangular, phylogram/time (only when valid), radial and sunburst layouts as plain coordinates | Working |
| [`@captiveorigins/onetaxonomy-react`](packages/react) | Server-renderable SVG tree, accessible keyboard outline, explorer (undo/redo, drag, overlays, inspector, clade selector, SVG/PNG/JSON exports), lesson player | Working |
| [`@captiveorigins/onetaxonomy-education`](packages/education) | Lesson decks (scenes, cameras, annotations, reveals) and worksheets with answers computed from the data | Working |
| [`@captiveorigins/payload-plugin-onetaxonomy`](packages/payload) | Payload plugin: sealed snapshots checked by checksum, staged and published, views pinned to them, import jobs that turn source files into staged snapshots, people's own proposals that a curator can accept into a new staged version, a bounded read API (name search, ancestors, descendants, MRCA, coverage, projected views, exports) behind the host's access rules, with optional tenant scope, and admin panels for publishing, import progress and deciding on proposals | Early (M7 parts 1 to 5): tested on Payload with an in-memory MongoDB and in the example site on this machine, not yet in a deployed site. Not on npm |
| [`@extantape/onetaxonomy-core`](packages/core) | Records, validation, bounded traversal, MRCA, tri-state monophyly, curated selections, coverage reports, snapshot checksums and diffs, views (actions, undo/redo, filters, highlights, projection), JSON Schema. No dependencies; runs anywhere. | Working |
| [`@extantape/onetaxonomy-import`](packages/import) | Source adapters (CSV/TSV, Darwin Core Archive, JSON, Newick, NEXUS, phyloXML), reconciliation, selective imports, community presets | Working |
| [`@extantape/onetaxonomy-layout`](packages/layout) | Rectangular, phylogram/time (only when valid), radial and sunburst layouts as plain coordinates | Working |
| [`@extantape/onetaxonomy-react`](packages/react) | Server-renderable SVG tree, accessible keyboard outline, explorer (undo/redo, drag, overlays, inspector, clade selector, SVG/PNG/JSON exports), lesson player | Working |
| [`@extantape/onetaxonomy-education`](packages/education) | Lesson decks (scenes, cameras, annotations, reveals) and worksheets with answers computed from the data | Working |
| [`@extantape/payload-plugin-onetaxonomy`](packages/payload) | Payload plugin: sealed snapshots checked by checksum, staged and published, views pinned to them, import jobs that turn source files into staged snapshots, people's own proposals that a curator can accept into a new staged version, a bounded read API (name search, ancestors, descendants, MRCA, coverage, projected views, exports) behind the host's access rules, with optional tenant scope, and admin panels for publishing, import progress and deciding on proposals | Early (M7 parts 1 to 5): tested on Payload with an in-memory MongoDB and in the example site on this machine, not yet in a deployed site. Not on npm |
| [`apps/studio`](apps/studio) | OneTaxonomy for Mac (Tauri 2): open checklists, Darwin Core Archives, Newick, NEXUS and phyloXML trees and snapshots; browse them; collapse weak branches, add a tree to an open checklist and link its tips to taxa (proposed by exact name, confirmed by you), compare two trees, and reroot or midpoint-root as a new tree; save views and sealed snapshots (never over the file that was opened), and save and open `.onetaxonomy` bundles (a documented ZIP with a manifest and checksums) | 0.1.0 on GitHub Releases. Not notarized, and it can't update itself yet |
| [`apps/payload-example`](apps/payload-example) | A small Payload and Next.js site that uses the plugin: two roles, public pages that draw published views on the server, and proposals that record people by id, with a public handle shown beside it. Runs on your machine with a throwaway in-memory database | An example. Checked over HTTP by its smoke test; nobody has clicked through its admin yet |

Expand All @@ -35,11 +35,11 @@ The full rules are in [scientific invariants](docs/architecture/scientific-invar
pnpm install
pnpm build
pnpm test
pnpm --filter @captiveorigins/onetaxonomy-examples start
pnpm --filter @extantape/onetaxonomy-examples start
```

```js
import { assessTaxon, indexDataset } from '@captiveorigins/onetaxonomy-core'
import { assessTaxon, indexDataset } from '@extantape/onetaxonomy-core'

const data = indexDataset(snapshot)
const result = assessTaxon(data.classification('traditional'), 'c:reptilia', data.phylogeny('archelosauria'))
Expand All @@ -49,7 +49,7 @@ const result = assessTaxon(data.classification('traditional'), 'c:reptilia', dat
Curate a selective import from a checklist (see `examples/selective-import.mjs`):

```js
import { csvAdapter, selectiveImport, toSnapshot } from '@captiveorigins/onetaxonomy-import'
import { csvAdapter, selectiveImport, toSnapshot } from '@extantape/onetaxonomy-import'

const read = await csvAdapter({ source: mySource }).read(csvText) // Darwin Core column names by default
const full = toSnapshot(read, { id: 'my-checklist', version: '2026-03', createdAt })
Expand All @@ -68,4 +68,4 @@ const { snapshot, coverage } = selectiveImport(full, {

## License

[PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0) for the software: free for noncommercial use, with attribution; commercial use needs a separate license from Captive Origins. Versions published before this change remain under MIT. Data you import keeps its own license; OneTaxonomy records it with every snapshot.
[PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0) for the software: free for noncommercial use, with attribution; commercial use needs a separate license from EXTANT APE. Versions published before this change remain under MIT. Data you import keeps its own license; OneTaxonomy records it with every snapshot.
6 changes: 3 additions & 3 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ One description everywhere: each `package.json` (`description`, `keywords`) is t

1. Bump the same `version` in every `packages/*/package.json` (lockstep, ADR 0007), update `CHANGELOG.md`, commit as `Release x.y.z`, tag `vx.y.z`, and push the commit and the tag.
2. The Release workflow builds, tests and **stages** every package on npm (trusted publishing, no stored token). Prereleases go to the `alpha` dist-tag once a package has a stable version; before that they are also `latest`.
3. The maintainer account approves each with 2FA: npmjs.com → avatar → **Staged Packages** → **Approve**, or `npm stage list` then `npm stage approve <id>`. Only then are they public.
3. The `mdgilardi` npm account, which owns the `@extantape` scope, approves each with 2FA: npmjs.com → avatar → **Staged Packages** → **Approve**, or `npm stage list` then `npm stage approve <id>`. Only then are they public.

`@captiveorigins/payload-plugin-onetaxonomy` (`packages/payload`) moves in lockstep but isn't staged yet: the Release workflow skips it until the owner has published its first version by hand and set up its trust. Then remove `grep -v -x payload` from the workflow's stage step.
`@extantape/payload-plugin-onetaxonomy` (`packages/payload`) moves in lockstep but isn't staged yet: the Release workflow skips it until the owner has published its first version by hand and set up its trust. Then remove `grep -v -x payload` from the workflow's stage step.

The first version of each package is published by hand (`pnpm publish --access public` in each `packages/*` folder, `core` first; pnpm turns `workspace:^` into the real version), because npm can only trust a workflow for a package that exists; then, once per package:

```bash
npm trust github <package-name> --repo CaptiveOrigins/onetaxonomy --file release.yml --allow-stage-publish --yes
npm trust github <package-name> --repo ExtantApe/OneTaxonomy --file release.yml --allow-stage-publish --yes
```
8 changes: 4 additions & 4 deletions apps/payload-example/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OneTaxonomy Payload example

A small Payload and Next.js site that uses [`@captiveorigins/payload-plugin-onetaxonomy`](../../packages/payload). It shows the plugin set up the way a real site would: accounts with two roles, public pages that draw published views, import jobs over uploaded files, and proposals that record a person by an id, with a public handle shown beside it and no email address.
A small Payload and Next.js site that uses [`@extantape/payload-plugin-onetaxonomy`](../../packages/payload). It shows the plugin set up the way a real site would: accounts with two roles, public pages that draw published views, import jobs over uploaded files, and proposals that record a person by an id, with a public handle shown beside it and no email address.

> **Status: an example, not a product.** It runs on this machine only, on a throwaway in-memory MongoDB, with made-up accounts and the repository's illustrative fixtures. Tested: 74 checks over HTTP with sign-in cookies (`pnpm smoke`, in CI), on `next dev` and on the built site, with Payload 3.93 and Next.js 16; the admin's panels clicked through in headless Chrome as the curator, in light and dark (`pnpm click-through`, 40 checks, not in CI: it needs Chrome and the network); and the three public pages looked at in headless Chrome by hand, in light and dark, at desktop width and at 520 px. Not tested: a screen reader, Safari, Firefox, a phone, Postgres or SQLite, or a deployed site.

Expand All @@ -24,7 +24,7 @@ From the repository root, with Node 22 or later and pnpm:
```bash
pnpm install
pnpm build # the packages the site uses
pnpm --filter @captiveorigins/onetaxonomy-payload-example dev
pnpm --filter @extantape/onetaxonomy-payload-example dev
```

The first run downloads a MongoDB binary (about 140 MB on a Mac, kept in `~/.cache/mongodb-binaries`). Then it prints the site's address, the two accounts and a password made up for this run:
Expand All @@ -48,7 +48,7 @@ Stop it with Ctrl-C. The database and the uploaded files go with it: every run s
## Check it

```bash
pnpm --filter @captiveorigins/onetaxonomy-payload-example smoke
pnpm --filter @extantape/onetaxonomy-payload-example smoke
```

This builds the site, starts it on a free port with its own database, and sends it requests the way a browser would. It takes about 15 seconds on a recent Mac. It checks that:
Expand Down Expand Up @@ -76,7 +76,7 @@ This builds the site, starts it on a free port with its own database, and sends
### In a browser

```bash
pnpm --filter @captiveorigins/onetaxonomy-payload-example click-through
pnpm --filter @extantape/onetaxonomy-payload-example click-through
```

This builds the site, starts it the same way, opens a Chrome or Chromium already on the machine with no window and a throwaway profile, signs in as the curator and does what a curator would: publishes a snapshot with the keyboard, changes and saves a draft view, runs the three import jobs, renames a finished one with Payload's own Save, accepts a proposal into a new version, pastes a sealed snapshot into the new-snapshot form, and publishes it with Payload's own status field. It takes about two minutes. It checks what the panels say at each step, what was sent, that the form under a panel doesn't repeat it, that the panel's parts are spaced apart as the browser lays them out, and that no page asks any site but this one, apart from Payload's code editor.
Expand Down
16 changes: 8 additions & 8 deletions apps/payload-example/app/(payload)/admin/importMap.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { SnapshotPanel as SnapshotPanel_577f3b5b503ff777fe77b01de8cacfa5 } from '@captiveorigins/payload-plugin-onetaxonomy/client'
import { ViewPanel as ViewPanel_577f3b5b503ff777fe77b01de8cacfa5 } from '@captiveorigins/payload-plugin-onetaxonomy/client'
import { ImportPanel as ImportPanel_577f3b5b503ff777fe77b01de8cacfa5 } from '@captiveorigins/payload-plugin-onetaxonomy/client'
import { ProposalPanel as ProposalPanel_577f3b5b503ff777fe77b01de8cacfa5 } from '@captiveorigins/payload-plugin-onetaxonomy/client'
import { SnapshotPanel as SnapshotPanel_6db27829aa354dff1e248cee79348b1f } from '@extantape/payload-plugin-onetaxonomy/client'
import { ViewPanel as ViewPanel_6db27829aa354dff1e248cee79348b1f } from '@extantape/payload-plugin-onetaxonomy/client'
import { ImportPanel as ImportPanel_6db27829aa354dff1e248cee79348b1f } from '@extantape/payload-plugin-onetaxonomy/client'
import { ProposalPanel as ProposalPanel_6db27829aa354dff1e248cee79348b1f } from '@extantape/payload-plugin-onetaxonomy/client'
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'

/** @type import('payload').ImportMap */
export const importMap = {
"@captiveorigins/payload-plugin-onetaxonomy/client#SnapshotPanel": SnapshotPanel_577f3b5b503ff777fe77b01de8cacfa5,
"@captiveorigins/payload-plugin-onetaxonomy/client#ViewPanel": ViewPanel_577f3b5b503ff777fe77b01de8cacfa5,
"@captiveorigins/payload-plugin-onetaxonomy/client#ImportPanel": ImportPanel_577f3b5b503ff777fe77b01de8cacfa5,
"@captiveorigins/payload-plugin-onetaxonomy/client#ProposalPanel": ProposalPanel_577f3b5b503ff777fe77b01de8cacfa5,
"@extantape/payload-plugin-onetaxonomy/client#SnapshotPanel": SnapshotPanel_6db27829aa354dff1e248cee79348b1f,
"@extantape/payload-plugin-onetaxonomy/client#ViewPanel": ViewPanel_6db27829aa354dff1e248cee79348b1f,
"@extantape/payload-plugin-onetaxonomy/client#ImportPanel": ImportPanel_6db27829aa354dff1e248cee79348b1f,
"@extantape/payload-plugin-onetaxonomy/client#ProposalPanel": ProposalPanel_6db27829aa354dff1e248cee79348b1f,
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
}
2 changes: 1 addition & 1 deletion apps/payload-example/app/(site)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import '@captiveorigins/onetaxonomy-react/styles.css'
import '@extantape/onetaxonomy-react/styles.css'
import './site.css'

import type { Metadata } from 'next'
Expand Down
2 changes: 1 addition & 1 deletion apps/payload-example/app/(site)/site.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* The example site's own page styles. The trees are styled by @captiveorigins/onetaxonomy-react/styles.css. */
/* The example site's own page styles. The trees are styled by @extantape/onetaxonomy-react/styles.css. */
:root {
--ink: #4c312c;
--text: #1d2330;
Expand Down
4 changes: 2 additions & 2 deletions apps/payload-example/app/(site)/views/[id]/ViewFigure.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Highlight, LayoutKind, Projection } from '@captiveorigins/onetaxonomy-core'
import { layoutFor, TreeOutline, treeViewBox, TreeView } from '@captiveorigins/onetaxonomy-react'
import type { Highlight, LayoutKind, Projection } from '@extantape/onetaxonomy-core'
import { layoutFor, TreeOutline, treeViewBox, TreeView } from '@extantape/onetaxonomy-react'

/** The parts of a stored view the drawing needs. */
export type StoredView = { layout: { kind: LayoutKind }; highlights: Highlight[] }
Expand Down
2 changes: 1 addition & 1 deletion apps/payload-example/app/(site)/views/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { projectedView } from '@captiveorigins/payload-plugin-onetaxonomy'
import { projectedView } from '@extantape/payload-plugin-onetaxonomy'
import config from '@payload-config'
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
Expand Down
Loading
Loading