Solid Memo welcomes contributions from anyone: a bug report, an idea, a deck, a translation, a report from your own Solid server, or code. This page says how to make one, and what happens to it.
Everyone taking part follows the code of conduct. How decisions are made is in GOVERNANCE.md; what is planned is in ROADMAP.md.
Talk in writing, in public. The project talks only in issues, Discussions, pull requests and their reviews, and commit messages. It has no chat and no calls, and something said elsewhere decides nothing until it is written up in one of them. Security and code of conduct reports are the only private exceptions (GOVERNANCE.md).
Found a security issue? Do not open an issue. Report it privately, as SECURITY.md says.
- Report a bug or suggest a feature. Open an issue and pick the form that fits. Questions and open-ended ideas go to Discussions.
- Propose or correct a deck. The ready-made decks live in
decks/, described in docs/deck-library.md. A deck can be written in the Studio and downloaded as a release file, which comes with the steps of a pull request (docs/studio.md). Open a "Deck or course" issue first, so we can agree on the subject and the license. - Write or improve a course. A course is a library deck with chapters and steps (docs/courses.md).
- Translate. The app speaks English, Swedish, Korean, German, Spanish and French. Korean, German, Spanish and French were translated with the help of AI, and corrections from native speakers are welcome (docs/i18n.md). A new language follows Adding a language.
- Report how Solid Memo works with your Solid server. Use the "Interop report" form. The servers the tests already run against are listed in docs/testing.md. To have the tests run against another one, use the "Suggest a server for the interop tests" form.
- Change the code. Start with docs/architecture.md and the index of the docs, docs/docs.ttl. A term you don't know is in docs/glossary.md.
Open an issue, and wait for a reply, before you change:
- the vocabulary in
ns/vocab/(docs/vocab.md), - the shapes in
ns/shapes/(docs/shapes.md), - the decks in
decks/(docs/deck-library.md), - the format of what the app writes to a pod (docs/migrations.md).
Pods, other apps and copies of decks depend on these, so a change is
hard to take back. Published versions are never edited: a published
decks/*/vN.ttl or ns/shapes/*/vN.ttl stays as it is, and a change ships
as the next version. A small fix elsewhere, such as a typo or an obvious
bug, needs no issue.
- Fork the repository and create a branch from
main. - Set up as the README says: Node 24 and npm, with
Docker for the end-to-end tests. The
dev container has everything.
Run
npm ci, andnpm run browsersonce:npm run checkandnpm run builddraw the docs' diagrams in Chromium, which it installs (on Linux, a machine that lacks the system libraries it needs gets them withsudo npx playwright install-deps chromium). - Make the change, with its tests and its docs; a new page in
docs/is listed in docs/docs.ttl. Run the app withnpm run devand try it. - Check that the change is done, as Done in AGENTS.md defines it. That list applies to people and agents alike: it says which commands must pass and which files are generated.
- Open a pull request against
main, and fill in its template.
The code follows the style guide, the
import rules and the testing policy:
100% coverage in every package, and new code ships with its tests.
Biome formats and lints the code: run
npm run format before you commit, and npm run check fails on what it
would still change (docs/style.md).
Text formats. Keep the repository diff-friendly: code in TypeScript, data in Turtle, docs in Markdown, configuration in JSON or YAML. Do not commit binary documents, such as word-processor files or spreadsheets. An image is committed only when the app shows it.
You may use AI tools, such as coding assistants and agents, to write code, tests, docs or anything else you contribute. AGENTS.md and the docs it points to are written for people and agents alike.
Whatever helped you write it, a contribution is yours, and you answer for it:
- Understand it. Read every line before you open the pull request, and be ready to explain any of it, and why it is there, in review.
- Check it. It meets the same bar as any other change: Done in AGENTS.md, its tests and its docs. A reviewer reviews it the same way, and does not review the output of your tools for you.
- Own the rights. Only contribute what you have the right to, as Licensing says. That holds for what a tool suggested too: you answer for its license as if you wrote it.
- Own what you post. An issue, a comment, a review or a commit message posted for you by a tool speaks for you. Read it before it is posted.
You do not have to say which tools you used. A pull request is not judged by how it was made, and is not declined for being written with AI; it can be declined for what it is, with a written reason, like any other.
Each commit records a decision, so its message says what and why. From
version 1.0.0 on, every commit follows
Conventional Commits 1.0.0,
Renovate's included: the release reads the messages to choose the next
version and to write the changelog (docs/releasing.md).
CI's commits job lints a pull request's commits with commitlint
(commitlint.config.mjs), and
npm run lint:commits lints your branch's before you push. Both check
that every commit has a body and names its issue, and both refuse a
merge commit, which commitlint does not lint: update your branch by
rebasing it on main. They refuse a chore(release) header too: it
is semantic-release's, for the release commit it makes on main. And
they refuse a message that holds one of GitHub's keywords that skip CI
([skip ci], [ci skip], [no ci], [skip actions],
[actions skip], or a skip-checks: trailer), in upper or lower
case: CI runs on every push to main, and the deploy only after it.
They refuse an HTML comment's <!--, --> or --!> and a spell
checker's directive anywhere in a message too: the release copies the message
into CHANGELOG.md, whose linters would read them.
The merge queue refuses those keywords, comment markers and directives
in a pull request's title too, and a title that starts as a breaking
change's footer, as the merge commit holds the title
(docs/releasing.md).
A commit commitlint skips, such as a revert, may not say it breaks.
npm run check:merges reads every commit's whole message for a comment
marker or a directive, as commitlint can miss part of one.
npm run lint:commits lints the commits since origin/main, so from a
fork, sync the fork's main with this repository's first, or run
npx commitlint --from upstream/main --to HEAD, with upstream the
remote that names this repository.
feat(studio): check a draft's links before it is published
A release with a link that does not answer leaves the learner with a
card that points nowhere, and a published release cannot be edited.
The check runs when the author publishes, not on every save, so a
draft can still hold a link that is not up yet.
Closes: #42
-
Header:
type(scope): subject. The scope is optional, and names the part of the project, such asstudio,i18n,decks,nsordeps. The subject is in the imperative, starts with a lowercase word and has no period at the end. The whole header is at most 100 characters. It says what the project or its user gains, not which files changed: "keep a deck's title in every language, shown in the reader's", not "update deck.ts". -
Type: one of these, by what the change is for users, and so for the version:
Type For Releases featSomething users, other apps or people who run their own copy can do that they could not A minor version (1.1.0) fixSomething that did not work as it should, a dependency the app ships updated included ( fix(deps))A patch (1.0.1) perfThe same, faster or lighter A patch revertA commit taken back A patch docsThe docs alone Nothing testThe tests alone Nothing refactorThe code, with nothing changed for users Nothing styleFormatting alone Nothing buildThe build, the scripts and the development tools Nothing ciThe workflows Nothing choreAnything else, such as a development tool updated ( chore(deps))Nothing A new deck, or a new version of one, is a
feat(decks): learners see it in the library. The site runs only released versions, so a change whose type releases nothing reaches it with the next version (docs/deployment.md). -
Body: after a blank line, why the change was made, and what was weighed, wrapped near 72 columns. Every commit has one, and commitlint refuses a commit without it.
-
Breaking change: a change that someone has to act on, or loses something by (docs/releasing.md), adds
!after the type or scope (feat(ns)!:), and aBREAKING CHANGE:footer that says what breaks and what to do about it; commitlint refuses one without the other. It releases a major version (2.0.0), so it needs an issue first. -
Footers: after a blank line,
Closes: #NN(orFixes: #NN,Resolves: #NN), naming the issue the change finishes; only these keywords make GitHub close the issue when the change reachesmain.Refs: #NNnames an issue or discussion without closing it: one the change relates to, or one that a series of pull requests finishes over time.Co-Authored-By:follows for each co-author. commitlint refuses a commit that names no issue, pull request or discussion by its number (#NN) anywhere in its message. If your change has no issue, open one first, or open the pull request and then amend the commit to name it (Closes: #NN) and push again. A security fix, made in private as SECURITY.md says, names its GitHub security advisory instead (Refs: GHSA-xxxx-xxxx-xxxx), since no public issue may describe it before the fix is deployed;npm run check:closestakes that for its pull request too. Renovate's updates are the other exception: a commit cannot name a pull request that has no number yet, so its body ends with theWhy:line ofcommitBodyin renovate.json5, and its merge commit names the pull request. -
One logical change per commit. Generated files go in the same commit as the change that generated them, and a new deck version goes in the same commit as the library index.
A pull request addresses one issue. Two unrelated fixes are two pull
requests. It closes the issue: its body, or one of its commits, says
Closes: #NN (or Fixes:, Resolves:), and CI's commits job fails
a pull request where neither does (npm run check:closes). A pull
request that is one step of its issue, and leaves it open, says
Refs: #NN in its body instead. Renovate's pull requests, which have
no issue, are left out. The job reads the body when it runs, so after
you edit the body, run the job again.
Solid Memo is run by volunteers: reviewing is done in their own time. A reviewer reads the code, the tests, the docs and any change of policy (the external rules the code follows), and runs the app when the change shows in it.
A reviewer checks that:
- the change keeps to the layers of docs/architecture.md and the import rules of docs/boundaries.md;
- it is written as docs/style.md says, and follows the standards in docs/standards.md and the rules in docs/policy.md;
- it agrees with what was decided before, in issues, in Discussions and in commit messages (GOVERNANCE.md);
- the source, any change of policy, the tests and the docs are all reviewed, not the code alone, and the docs say what the code now does;
- for a change users see, the tests pass when the reviewer runs them, and the change works when the reviewer tries it in the app.
What a contributor can expect:
- We aim to respond to an issue or a pull request within two business days. That is a target, not a promise.
- A contribution that is declined or closed always gets a written reason.
- Every pull request must pass CI before it is merged.
The review of a deck or a course looks at its form: that the library's checks pass, and that its license and its sources are stated. It does not check the facts on the cards; the deck's provenance says how they were researched and checked (docs/deck-library.md).
The project has one maintainer for now, so a contribution is reviewed by him. His own pull requests are merged without a second reviewer, as a known, temporary exception (GOVERNANCE.md). If you would like to review, say so in an issue or in Discussions.
Contributions are accepted under the license they are published under:
the MIT License, for everything in the repository, docs/ and
ns/ included, unless a file says otherwise. A deck carries its own
license in dcterms:license, and the vendored shapes keep theirs. By
opening a pull request, you agree that your contribution is licensed
that way. There is no contributor license agreement, and you keep your
copyright.
Only contribute what you have the right to: your own work, or work under a license compatible with the file it goes into. Name the source of a deck's content in its provenance (docs/deck-library.md).
Every file says so in a machine-readable way, as
REUSE has it: a new source file starts with
the SPDX header, and a new deck is listed in REUSE.toml
under the license it states. docs/style.md
says how. npm run lint:reuse checks it (it needs the
reuse tool:
pip install -r scripts/requirements-lint.txt installs it, or
uv run --no-project --with-requirements scripts/requirements-lint.txt reuse lint
runs it without installing it),
and so does CI; npm run check checks only that each deck is listed
under the license it states.
English is the authoritative language of the code, the docs and the
discussions. Write in plain English with American spelling ("license",
"color", "organize"); npm run lint:spelling, part of npm run check,
checks the Markdown and the English messages for it
(docs/style.md). The docs follow the writing
rules in docs/style.md, which
npm run lint:prose checks. A
translation of the app follows the English messages in
packages/ui/src/i18n/en.json.