Version 3.5.0 · All versions

All pages

These are the docs as released with Solid Memo 3.5.0. Read the latest docs.

Studio

Solid Memo Studio is a second app beside Solid Memo, in the same page, at https://solid-memo.com/#/studio. It is where users manage their decks, and where creators write decks and courses and publish them.

To manage, it shows the decks of an instance in a table, changes several of them at once, and arranges them into groups. It lists a deck's cards to search, filter, sort, select, edit at once, and move or copy to another deck. It edits one card, its wrong options and its schedule, with its history of answers. It shows a deck's schedule to come, its lapses and leeches, and everything wrong with a deck or the instance. It lists the decks copied from the library or added from a link, and upgrades them to newer releases. It exports decks as Turtle or JSON-LD files, and imports them. It edits what a deck says of itself (its authors and license among it), a course's progress, and the instance's name and catalog.

To create, it keeps the drafts of releases a creator writes in the instance. A draft is started from nothing, from a deck, as the next version of a release, or from a release saved as a file. It is written: what the release says of itself, a course's outline, its chapters, steps and questions, their wrong options, and how it was made and from what. It is checked by every rule a release is held to, previewed as the library will list it, played in a sandbox as learners will play it, compared with the release it follows, and deleted. A draft is released by publishing it in the pod, where anyone can read it, or by saving it as a file for the library. The releases an instance published are listed, each to share or to start the next version of. A learner adds a published release in Solid Memo from its link (deck-library.md).

What it shares with Solid Memo

The Studio is its own package, @solid-memo/studio in apps/studio/, with its own screens, tests and router. It sits on the same layers as Solid Memo's app (boundaries.md):

Both apps are one page. So they share one session and one login (authentication.md): a link from one to the other is a hash link, which keeps the user signed in. A login set off in the Studio, or a reload of one of its routes, comes back to the Studio: the redirect drops the hash, and the session gateway puts it back. They also share the use cases, their cached queries (an edit in one shows in the other), and the browser's storage: the guest's pod, the language and theme chosen on this device, and the update journal. So while a tab moves a guest's study into a pod, the others refuse to write to it (guest-mode.md).

The Studio's code is a chunk of its own. The page fetches it the first time a Studio route opens (loadStudio, in apps/web/src/main.tsx), so a learner who never opens the Studio never downloads it. Meanwhile, under the masthead, the page says the Studio is opening; if the chunk cannot be fetched (offline, or after a new deploy), it says so.

Layout

The Studio is a workspace for authors, not a column to read (shell/StudioShell.tsx). It takes the whole window, and the window does not scroll:

Tabs. A deck's screens have the deck's tabs, under the trail (shell/ContextTabs.tsx, a navigation named Deck): Cards (a card in the inspector among them), Schedule, Health, About, and Export, which opens import and export with the deck checked. A draft's screens have the draft's (named Draft): Overview (its chapters and steps under it), Cards (its questions under them), Check, which counts the problems the release check finds ("Check 3", heard as "Check 3 problems"), Preview, Trial, Changes (only for a draft that follows a release) and Release. The one shown is marked aria-current="page"; on a page under a tab (a card, a chapter, a step or a question), that tab is marked aria-current="true", so only the page's own tab is the page. Both look the same, bold and underlined. Which tabs a route has, and which is shown, is worked out from the route alone (shell/workspaceLayout.ts). The card inspector's tabs and the release check's policies are tabs of the same kind.

The sidebar and the tabs are links: each is a Back stop. Hiding the sidebar, the aside or the dock, opening a drawer, or sizing a pane, is not.

What each screen has around it, worked out from its route alone (shell/workspaceLayout.ts: sectionOf, deckOf, contextTabsOf, asideOf):

ScreensThe sidebar marksTabsThe asideThe dock's problems
the instancesno sidebar: no instance is opennonenonenone
HomeDecksnone (its views are its own)the deck at the table's cursor (Deck)none
the library copiesLibrary copiesnonethe upgrade of the copy at the table's cursor (Upgrade)none
groups, drafts, releases, the instance's health, import and export, name and catalogthat sectionnonenonenone
a deck's cards, schedule, health and about screenthe deckthe deck'snonewhat the deck's health found
a cardthe deckthe deck's, Cards marked as the card'son a wide screen, the card (Card), the deck's cards in the main content; else the card is the main contentwhat the deck's health found
a draft's overviewthe draftthe draft'snoneits release check's
a draft's other screens: its cards, check, preview, trial, changes and releasethe draftthe draft'sa course's outline (Outline)its release check's
a chapter, a step, a questionthe draftthe draft's, Overview or Cards marked as theirsa course's outline, the item edited markedits release check's

What is saved, and when. A draft's changes are saved as they are typed or made, and the status bar says so (below). Elsewhere a button, a menu's item or a drag changes the pod at once (asking first when something is lost), and what is typed into a form waits for its Save: a card's content, its wrong options, a deck's or the instance's about screen, a cell edited in place (Enter saves it). A card's content changed and not saved, and a cell changed and not saved, are asked about (Save, Discard or Stay) before anything leaves them, Back and Forward among it (below). How the Studio is laid out is kept on the device as it changes (Density, below), never in the pod.

Its look is calm: headings in the body's type, sections flat with a thin border, and no tilt, stamp or pop as a screen comes in. Its colors are Solid Memo's, so the theme and an instance's colors carry over (theme.md).

Density. Rows and controls are compact, about 28px high, on every screen and on touch too: above WCAG 2.2's 24px minimum target. The status bar's Comfortable makes them 44px; Compact goes back. The choice is kept on the device, never in the pod (shell/prefs.ts, key solid-memo:studioLayout.v1; storage that is missing or refuses gives compact), and changes no history entry. So are whether the sidebar, the aside and the dock are shown, and the aside's width (the outline's, and a card's beside its deck's cards) and the dock's height (each kept within its bounds), and the columns each table hides. What the device keeps of each instance is under a key of its own (solid-memo:studioInstances.v1, by the instance's URL): the views saved of Home and of the workbench (below), the decks and drafts pinned, and those opened last. Storage that refuses keeps them for the page's life only.

Keys. The Studio has one listener of keys, in its shell (shell/useShortcuts.ts), and its keymap is data (shell/shortcuts.ts), each key of one scope: anywhere in the Studio, or in a table (useRowKeys, shell/useRowKeys.ts). The key help lists them by their scope:

KeysWhereWhat they do
Ctrl+K, Cmd+Kanywhere, a field toosearch, go to or run a command, in the palette (below); Search or run… in the header does too
?anywherelist every key, in a dialog; ? Keys in the status bar does too
/anywherego to the screen's search or filter: Home's, the workbench's, a draft's cards', the releases'
F6, Shift+F6anywhere, a field toogo to the next region, or the one before: the header, the sidebar, the main content, the aside, the dock (each while it is shown), the status bar
g d, g g, g r, g p, g h, g l, g twith an instance opengo to its decks (Home), groups, drafts, releases, health, library copies, or import and export
g ianywherego to the instances
Ctrl+Bwith an instance openshow or hide the sidebar; on a narrow screen, open or close the drawer
Ctrl+\Home, the library copies, a course draft's screens but its overview; a card beside its deck's cardsshow or hide the aside (the deck, the copy's upgrade, the outline); on a screen not wide, open or close its drawer; close the card, going to the deck's cards
Ctrl+Jwith an instance openshow or hide the dock
F8, Shift+F8a deck's or a draft's screens, a field toogo to the field of the next problem in the dock's list, or the one before, round from either end (below)
], [a deck's or a draft's screensgo to the next tab, or the one before, round from either end
1 … 9a deck's or a draft's screens, but the trialgo to the tab of that number; in the trial they are the learner's, a grade or an option
j, k, x, Entera table: the card workbench (a card beside it too), Home, the library copies, a draft's cards, the releasesthe next row, the one before (beside a card, show it; on Home and the library copies, show it in the aside), select it, open it (below)
Shift+J, Shift+K, Escapea table whose rows are selectedselect the row and the next, or the one before; clear the selection
.a table whose rows are selectedopen the first menu of the selection's bar, or go to its first button (below)
F2a table with cells to edit: Home, the card workbenchedit the cell the focus is in, or the row's first that can be edited (below)
Ctrl+Enter, Cmd+Entera text of several linessubmit its form, as Enter does a text of one line (ui's LangTextField); the key help leaves it out
Escapea drawer, a dialog, the palette, a cell edited in placeclose it, the cell unchanged

Single-key shortcuts (a character alone: ?, /, the g chords, [, ], 1 … 9, and a table's j, k, x, ., Shift+J and Shift+K) can be turned off in the key help, which the device remembers (characterKeys, Density), so that speech input, or a key pressed by mistake, sets nothing off (WCAG 2.1.4); the keys with Ctrl, the F keys, Enter, Escape and the buttons work still. A character typed with AltGr, or with Option on a Mac ([ is AltGr+8 on a Swedish keyboard), is the character, as one typed with Shift is; Windows reports AltGr as Ctrl+Alt, so there Ctrl+\ cannot be told from a \ typed with AltGr, and the aside's button, or the palette, shows or hides it instead.

A pair such as g d is pressed one key after the other, the second within 1.5 seconds; a key held down counts once, so a held g is not g g. A g chord, and a tab's key, goes where a link would, and is a Back stop as a link is, except to the page already shown. While the user types in a field (a text, a search, a list to choose from), the field keeps its keys, Ctrl+K, F6, Shift+F6, F8 and Shift+F8 aside. A key goes first to what has the focus, which may take it (a menu's arrows, say); then to the screen's own keys (useScreenKeys), then to the Studio's. While a dialog is open, keys are the dialog's, wherever the focus is (on the page's body, once a press on the dialog's text took it from its buttons), but Ctrl+K (Cmd+K), which closes the dialog and opens the palette in its place, rather than reach the browser; while a menu is open (Columns, say), every key is the menu's, so j and k do not move the table under it. A field or button with a key says so to screen readers (aria-keyshortcuts): the search or filter / goes to is the one marked so. The key help is a modal dialog, the browser's own (shell/Dialog.tsx): Escape or Close closes it, and the focus goes back where it was. A saved view renamed has the focus once it shows its new name, and one removed gives it to + Save view: the menu button the dialog would give it back to went with the view.

A splitter (shell/Splitter.tsx) is a separator, as screen readers know one, that takes the focus: its value is the pane's size in pixels, between its least and its most. Left and Right (Up and Down, for the dock's) move it 16 pixels, Home and End to either end; the pointer drags it. The sums are in shell/splitter.ts.

Filters and the selection. A table's filters (the workbench's, Home's, a draft's cards') are one row over it, wrapping only on a narrow screen, each label small and beside its field, and the counts at the row's end. Rows selected in a table (the workbench, Home, the library copies, a draft's cards) bring up a bar under it (shell/BulkBar.tsx), a group named for them ("Selected cards"), which stays in sight at the foot of the main content as the table scrolls: how many are selected, what can be done with them all at once (a few buttons, and menus of the rest), and Clear selection. It is there only while some are; a status line, there throughout, says how many. . opens its first menu.

Questions and forms in a dialog. Whatever loses something asks first, in the Studio's own dialog, not the browser's (shell/ConfirmDialog.tsx, useConfirm): a heading, the question, the action (it has the focus) and Cancel; Escape or Close does nothing either. So does deleting cards, decks, a draft or a part of it, forgetting progress, restarting a course, and removing data from the pod on the health screen. The components the Studio shares with Solid Memo ask through ui's AskFirst (askFirst.ts), which the Studio gives its useConfirm, and Solid Memo leaves to the browser's confirm: removing a card, deleting a wrong option, and deleting an instance. Deleting a group, or a deck from the groups' tree, still asks in the browser's: its focus, once the deck is gone, is worked out from the button the question leaves it on. A bulk edit that needs to be told more (a language, a day, a deck, a group) asks in a dialog too, not in the page above the table.

Editing in a cell. Some cells are edited where they are (shell/EditableCell.tsx): on Home a deck's direction and pace, on the workbench a card's front and back. Such a cell is a button that shows what the cell does (named "New per day of Kanji N5: 20 (instance)"), outlined as it is pointed at; a click, Enter on it or F2 on its row opens a field in the cell (F2 opens the cell the focus is in, else the row's first: a card's front, whose cell is its link, opens so alone). Enter, or its Save, saves it the way the bulk edits do, and the cell has the focus again; Escape, or Cancel, leaves the value as it was. A value refused stays in the field, marked invalid, and the screen says why. A value changed and not saved stays in its field as the focus goes elsewhere, and is asked about before it is left, as a card's edits are (below); one left unchanged closes as the focus leaves it. Saved from that question, it is saved as the cell is edited then: a deck's pace saved from another cell of its row since is kept. Discarded there, its field closes, the edit gone from sight as from the question. Enter and Escape that an input method takes, to end or drop a syllable it composes (Korean, say), are the input method's. Nothing is saved unasked.

Saved views. Beside a table's own views (Home's and the workbench's, below), + Save view saves the table's query under a name, asked in a dialog (shell/SavedViews.tsx). Each view saved is a tab with the others, pressed while the table is in its query, which a press puts it in (no Back stop, as a view's tab); its menu renames it or removes it, once the user confirms. A name is a view's own: a view saved or renamed under a name taken replaces that view. The device keeps them for each instance (Density), a view of the workbench for every deck of it. A view keeps the query as its URL has it (q=kanji&show=due; shell/views.ts), the workbench's without its page.

The dock

The dock (shell/BottomDock.tsx) has two tabs, a tablist (Left and Right go from one to the other):

F8 goes to the field of the next problem in the Problems list, Shift+F8 to the one before, as a link does, round from either end: from the problem gone to last while its page is shown (so two problems in one field are each gone to), else from the page shown (stepProblem in shell/workspaceLayout.ts).

Its stylesheet is studio.css, over the site's style.css, and holds every rule of the Studio's own: the workspace's, and its screens' (its tables, badges, filters, tabs, the outline, the listing preview), which style.css holds none of. The shell imports it, so it comes with the Studio's chunk and a learner never downloads it. Solid Memo shares the page, and the app switches between the two without a reload, so every rule holds under <html data-surface="studio">, which the shell sets while it is shown and takes away after; the density is data-density beside it. The screens' rules come first, their scope in :where(), which weighs nothing: so the workspace's rules after them, the density's among them, win over them. A test (studio.test.ts) checks the rules through the cascade, and that Solid Memo's screens are left as they were.

Quick open and commands

The palette (shell/CommandPalette.tsx, a dialog named Quick open) opens with Ctrl+K (Cmd+K on a Mac), or with Search or run… in the header. It finds, as the user types:

It lists what the workspace has read, from its cache, as it was when the palette opened: it reads nothing from the pod, so a deck's cards are there once a screen of the deck has read them. Which entries there are, and how they rank, is worked out apart from the page (shell/commandIndex.ts): an entry matches when its name holds the letters typed in their order, case and accents aside ("kn5" finds Kanji N5); a letter at the start of a word, or right after the one before, counts for more, and a shorter name a little. The best come first, each group under its heading where its best is, 50 at most. With nothing typed, the decks and drafts pinned in the sidebar come first, under Pinned; then the entries the device remembers as used last, under Recently used (eight at most, those pinned aside, the last first; shell/prefs.ts, key solid-memo:studioRecent.v1, each by its group and where it goes, or by its command), then the rest but the cards. Each entry shows the keys that do the same, if any.

The field is a combobox over the list, as screen readers know it: the focus stays in the field, and the entry picked is its aria-activedescendant. Down and Up pick the next entry or the one before, round from either end; Enter, or a click, chooses it; Escape closes the palette, as Ctrl+K (Cmd+K) does again from anywhere in it, rather than reach the browser; the focus goes back where it was. An entry that goes somewhere goes as a link does, a Back stop, except to the page already shown; a command is done once the palette has closed.

A screen adds its commands while it is shown, and only those it can do at the moment (useScreenCommands, in shell/useScreenCommands.ts):

The shell's own commands show the keys, and show or hide the sidebar, the aside (where there is one; beside a card, Close the card) and the dock. A screen's:

ScreenCommandWhat it does
DraftsNew draftgoes to the new draft's form, while a draft can be made
Health, an instance's or a deck'sCheck againchecks again, while it is not checking
Import and exportExport n decksexports the decks checked, as the form's button does, while some are and none is being exported

Routes

The Studio has its own hash router (src/ui/router.ts). It keeps the hash with the same core as Solid Memo's router (routerCore.ts in ui, routing.md). Its hashes all start with #/studio (STUDIO_PATH, isStudioHash in ui's router); Solid Memo's are the others.

HashScreen
#/studio/instancesthe instance picker. A new instance is made in Solid Memo, so its link goes to Solid Memo's storage picker.
#/studio?instance=…[&q=…&sort=…&order=desc&show=…&deck=…]Home: every deck of the instance as a table (below). q filters it, sort names the column it is sorted by, and order=desc sorts it the other way. show is the view its tabs show: due, courses, copies or attention; every deck without it. deck is the deck beside the table, at its cursor.
#/studio/groups?instance=…Groups: the instance's decks arranged into groups, as Solid Memo's deck list arranges them.
#/studio/cards?deck=…[&q&field&lang&state&has&sort&order&page&size]the card workbench: a deck's cards as a table (below). The deck names the instance: its catalog's folder.
#/studio/card?deck=…&card=…[&tab=distractors|schedule|history&field=…][&q&in&lang&state&has&sort&order&page&size]the card inspector: one card, its content, with tab=distractors its wrong options, with tab=schedule its review state, or with tab=history its answers (below). The content is the default tab, and is left out of the URL. field opens it at one field: a text of the content (front, backNote…), or a wrong option by its id. The rest is the query of the workbench beside the card, as the workbench's own, but in for its field (where to look); without it, the deck's cards as listed.
#/studio/schedule?deck=…a deck's schedule: the reviews to come, its intervals and eases, its lapses and leeches (below).
#/studio/about?deck=…a deck's about screen: what it says of itself, how it is studied and, for a course, the learner's progress (below).
#/studio/instance?instance=…the instance's name and its catalog (below).
#/studio/health?instance=…[&deck=…]everything wrong with the instance, or with one of its decks (below).
#/studio/library?instance=…the instance's copies of library releases, the newer releases and what upgrading would change (below).
#/studio/transfer?instance=…[&deck=…&deck=…]import and export: the instance's decks to save as files, those of each deck checked, and a file to make a deck of (below).
#/studio/drafts?instance=…the instance's drafts of releases, and a new one to start (below).
#/studio/draft?draft=…[&field=…]a draft's overview: what the release says of itself and, for a course, its outline (below). draft is the draft's release document, which names the instance.
#/studio/chapter?draft=…&chapter=…[&field=…]a chapter of a course draft, by its id (below).
#/studio/step?draft=…&step=…[&field=…]a step of a course draft, by its id (below).
#/studio/question?draft=…&card=…[&field=…]a card of a draft, by its id: a course's question, or a deck's card (below).
#/studio/draft-cards?draft=…[&filter=…&lang=…&sort=…&order=desc&page=…]a draft's cards as a table (below): filter is unasked, askedTwice, fewDistractors or retired, lang a language, sort the column it is sorted by (front, back, place, distractors or id; by id without it), and order=desc the other way.
#/studio/check?draft=…[&policy=library]a draft's release check (below): for a release in a pod, or with policy=library for the Solid Memo library.
#/studio/preview?draft=…a draft as the library will list it (below).
#/studio/trial?draft=…[&chapter=…&part=review]a draft played in a sandbox (below): a course's page, with chapter (an id) that chapter's steps, and with part=review its final review; a deck's study.
#/studio/diff?draft=…a draft against the release it follows (below): what it changes, and what learners' copies would get.
#/studio/release?draft=…[&field=…]what a draft's release says of itself beyond its listing, how it was made and from what (below), then its publishing and its download (below).
#/studio/releases?instance=…the releases the instance published (below).

#/studio/ and its query count as #/studio. Anything else under #/studio, #/studio itself among them, is the default route: the only instance's Home, or else the picker. An unknown instance falls back to the picker, an unknown deck to its instance's Home, and an unknown card (one removed, say) to its deck's cards. A chapter, step or card a draft does not have (one deleted, say) falls back to the draft's overview. field opens a draft's screen at one of its fields, where the release check links: the overview's title, description or outline; a chapter's title, description, steps or review; a step's theory or questions; a question's text (front, backNote…), its wrong options (distractors) or one of them (distractor:<id>); the release screen's versionNotes, languages, themes, license, publisher, authors, making, sources or checks. A field the screen does not have is left out. Like Solid Memo's fallbacks, these replace the history entry.

Changing Home's filter, sort or view, the deck beside its table, the workbench's query, the inspector's tab, the decks checked to export, or the view of a draft's cards, replaces the history entry: it is the same screen, looked at another way, so Back leaves it. So does going from card to card beside the deck's cards (j and k, Previous and Next); opening a card from the list is a Back stop, as is opening a deck from Home or a card from a draft's cards with Enter.

Solid Memo links to Home as #/studio?instance=… ("Open in Studio", in its instance bar, each deck's actions menu and the deck page; routing.md). A deck's link opens its instance's Home too.

The trail is Instances › Decks, then › Groups on the Groups screen, › Cards of deck in the workbench, › Cards of deck › card in the inspector, › Cards of deck › Schedule on a deck's schedule, › About deck on a deck's about screen, or › Name and catalog on the instance's screen, › Health on the instance's health, › Health › deck on a deck's, › Library copies on the library copies, › Import and export on import and export, › Drafts on the drafts, and › Releases on the releases. A draft's screens go on from › Drafts: › draft on its overview, › draft › chapter on a chapter, › draft › chapter › Step 2 on a step, › draft › Cards on its cards, › draft › Cards › card on a question, › draft › Release check on its check, › draft › Listing preview on its preview, › draft › Trial on its trial, › draft › Changes on its comparison, and › draft › Release on its release metadata (crumbsOf, in shell/titles.ts, from the route and the deck, card or draft it names). The document title is the trail's last step and "Solid Memo Studio". After a move, the screen's heading takes the focus (useScreenFocus). The header has a link back to Solid Memo, at the open instance's decks when there is one, and the masthead names the Studio. The landing page and the Pod connection screen have the link too (headerLink in STUDIO), so a visitor who is not signed in can go back. The Studio's links into Solid Memo (a deck's page, its preferences, a new instance) are Solid Memo's own hashes.

The Studio's old address, /studio/, is a small page (apps/web/public/studio/index.html) that sends /studio/#/x on to ../#/studio/x. Its script runs under a Content Security Policy that allows it alone, by its hash; a test (studioRedirect.test.ts) checks both.

Home

Home (DeckTableContainer) is a table with a row per deck. Its columns are:

Beside a deck's name, its problems (health), "3 problems", a link to the deck's health. A deck is checked only once its row is on the screen (IntersectionObserver), so a long table checks only the decks the user scrolls to. A deck with no problem gets no badge.

Beside a copy's name, "Release 2 out" when a newer release of it is out, a link to the library copies. It too is looked up once its row is on the screen: one read of the library's index for every copy (listLibraryUpdates), and for a copy from a link its creator's catalog. A copy up to date, or one whose newer releases cannot be known, gets no badge.

The instance's other sections, and its drafts, are in the sidebar; with no decks yet, Home says to make or import them in Solid Memo.

The table starts in the order the user arranged the decks. A column's name sorts by it, then the other way, then back to that order; the sorted column says so (aria-sort). A figure not known yet sorts last, either way. The filter keeps the decks whose name, in any language, or whose groups' names hold its text, case and accents aside. The domain does both (deckTable.ts).

Views. Tabs above the table (a group named Views, the one shown pressed) each keep the decks of one kind, and count them: All, Due today (cards due today, and those whose cards are still counted, their count a loader meanwhile, so none is hidden while it may be due), Courses (copies of a course), Library copies (copies of a release, from the library or a link) and Needs attention (invalid data, or cards that could not be read). Each is a preset of Home's query (shell/views.ts): it sets the view (show), and keeps the filter's text and the sort. A deck's problems (health) and its newer release are found only as its row comes into view, so no view keeps decks by them. After them, the views the user saved, and + Save view (Layout).

Editing a deck in its row. A deck that may be changed has its direction, new cards per day and reviews per day edited in their cells (Layout): a choice of the directions, or a whole number, which left empty follows the instance's pace again. Each is saved as the bulk edits save it, for that deck alone, and said as they say it ("Set the pace of 1 deck.").

The table is a DataTable: j and k move its cursor, x selects a deck, and Enter opens the deck's about screen. Its Columns menu shows or hides each column but the deck's name; the device keeps which are hidden, as it keeps the layout, and every column is shown until hidden there. The workbench and a draft's cards have the menu too, each table its own.

The deck beside the table. The deck at the table's cursor is in the aside, named Deck (DeckPeekContainer): its groups, its cards, today's due and new cards, when it last changed, its health, and links to its tabs (Cards, Schedule, Health, About, Export). It reads the table's own queries, so it reads nothing the table has not. The URL names it (deck), each move of the cursor replacing the history entry; a deck the instance does not have shows none. Before the cursor has moved, the aside says how to show one.

Decks are selected by their checkbox, or all those shown at once, and the selection's bar (Layout) offers what can be done with them: Edit (a menu of the first three, each asking how in a dialog), Export and Delete. The selected decks the table shows can be (a selected deck the filter hides stays selected, but is left alone until it is shown again):

Each is one write of catalog.ttl, made in turn with the other writes of the catalog, and the table is read afresh after it (data-model.md). A deck set aside cannot be selected, nor can any deck until the data check is done (below). While the arrangement is set aside, no deck is moved into a group.

Card workbench

The workbench (CardWorkbenchContainer) lists a deck's cards in a table, a row each: the front (a link to the card), the back, when it is due, its interval and its ease, its lapses (below), when it was added, and its id. A retired card says so. A card studied both ways shows the direction that needs the most work: the earliest due day, the shortest interval and the lowest ease. A deck that is a copy of a course says so: its cards are the course's questions.

The URL holds how the cards are looked at. The domain applies it (cardQuery.ts):

A change of the search or a filter goes back to the first page.

Views. Tabs above the filters (a group named Views) put the cards in one state: All (none), Due, New and Leeches; the one the query is in is pressed, none when its state is another. A view keeps the rest of the query, and goes to the first page. After them, the views the user saved (Layout), each a whole query, for any deck of the instance.

Editing a card in its row. A plain text card (not in Markdown) has its front and back edited in their cells, each side only while its text is in one language, stated (Layout). It is an edit of that card alone (setText in cardBulk.ts), planned and written as the bulk edits are: the status line says it, and Undo undoes it (below). A side left empty is refused; one written as it was is closed with nothing written. A card in Markdown, or a side in several languages, is edited in the inspector.

The table is the Studio's DataTable (shell/DataTable.tsx): its header row and its first column (the checkboxes and the fronts) stay in sight as it scrolls. Cards are selected by their checkbox, or all those on the page at once; a click with Shift held selects, or not, every card from the one checked last. The selection stays while the view changes, and the selection's bar under the table counts it (Layout). The keys say so, quietly, under the bar: j and k move the cursor between the rows (the focus goes to the row's front), x selects the row, Shift+J and Shift+K select on from where the selection was last made, Escape clears the selection, Enter opens the card, F2 edits the row's front, or the cell the focus is in, and . opens the bar's first menu. The cursor's row is marked, as is each row selected. j and k work wherever the focus is: from outside the table, they go on from the cursor's row, or to the first. x, Enter and the Shift keys act on the row the focus is in, or, with the focus on the page's body, the cursor's; Enter on a row's link or button is the link's or the button's. The keys are left to the search and the filters while they have the focus, and to whatever took them first; they are the screen's own keys of the Studio's (Keys; useRowKeys). What is selected, how the table is sorted, where the cursor goes and which columns show is worked out apart from the page (shell/tableModel.ts). Home, the library copies, a draft's cards, the releases and a card's answers are DataTables too, each with the keys and the marks it has use for. A row that cannot be selected now (a deck set aside, a copy with nothing to upgrade) has its checkbox disabled, or none, and a range or a page's checkbox passes over it. Its Columns menu shows or hides each column but the first (Home).

The selected cards can be edited at once (below).

The lapses column counts each card's lapses, in either direction, in the answer log: its reviews graded below 3, not its first answer, an answer the same day after another, or a course answer (statistics.md). The log goes back only so far, so the row of filters ends with the month of the deck's first answer in it: "since March 2025", after how many cards the query keeps. The workbench reads the whole log once (loadAnswerLog). While it is read, the cards show without their lapses, and a query that needs them (state=leech, sort=lapses) waits. When it cannot be read, the line says so, and such a query shows why. The deck's tabs go to its schedule, its health and its export (import and export, the deck checked).

A card opens in the card inspector: on a screen wider than 64rem beside the workbench, which stays as it is, its query, selection and Undo with it. The workbench reads the cards and the review states with the same queries as Solid Memo, and the instance's preferences for today's study day. A test keeps a query over 5,000 cards linear: timed at two sizes on the same machine, four times the cards take less than ten times as long.

Bulk edits

The selected cards the query keeps can be edited at once, on any page (CardBulkActions), from the selection's bar (Layout). A selected card the query hides stays selected, but is left alone. The bar groups the edits: Edit (Write in Markdown, Write as plain text, State language, Find and replace), Schedule (Set due date, Forget progress), Move or copy (Move to deck…, Copy to deck…), then Retire, Restore and Delete. An edit that needs to be told more asks in a dialog; one that loses something asks first (Layout). While the deck may not be changed, the bar has Clear selection alone. The edits are:

The domain plans each edit (cardBulk.ts, planCardEdit). A card it would change must pass the card editor's validation (validateCardContent), else it is left as it is. So is a card the edit does not change, and one whose side or wrong option a replace would leave empty. A note or a label left empty is removed, as in the card editor. Text whose language is not stated cannot be changed before its language is.

The use case editCards plans the edit again on the cards as they are now. It writes it in one write of the cards document, made only if the document is still as read (If-Match, one PUT: data-model.md). Then it writes the review states of deleted cards in one write of the reviews document, and brings the deck's schedule in the digest up to date. When the cards document changed meanwhile, or was made since it was read as absent, it reads it and plans again, three times in all. Once that plan is not the one the user previewed, it stops with "changed elsewhere", and nothing is written. The deck's catalog entry is untouched, as when a card is saved in Solid Memo. The end-to-end tests hold this against real servers, a change made by another app during the edit included (cardEdits.integration.test.ts).

After an edit, the status line says what it did, and Undo undoes it: undoCardEdit writes the plan's inverse the same way, the deleted cards' review states included. It does so only while the cards are as the edit left them. The plan is kept in the page, never stored, so Undo goes with the next edit, another deck, or leaving the page.

No edit takes a backup first. Solid Memo keeps no backups: the format update and the library upgrade write each document in place, and the backups the copying update made are gone (migrations.md). A deletion or a find and replace over many cards is guarded by its preview, its confirmation, and Undo while the page stays open. What Undo cannot bring back is what Solid Memo never knew: triples another app put on a deleted card or its wrong options, and a second state of a card that was never read.

Card inspector

The card inspector (CardInspectorContainer) shows one card of a deck, named in its heading, with a link to its page in Solid Memo. On a screen wider than 64rem it is in the aside, beside the deck's cards in the workbench, as the card's route has them (Layout): the card's row there is marked (aria-current) and is the cursor's, and a card opened from the list takes the focus to its heading; closed, the cursor stays on its row, and the focus goes back there. Previous and Next at its heading's end, or k and j, show the card before it in the list, or the one after (each at its page of the list), on the same tab, with no Back stop; at either end of the list, or for a card the query does not keep, there is none. A card opened from elsewhere (the palette, a deck's schedule, a link) puts the list at its page, so that its row is in sight, replacing the history entry. Below that width the inspector is the main content alone.

Its four tabs are links, the one shown marked (aria-current):

The domain makes each change (distractors.ts):

A copy of a library deck or a course says how the card stands to the release it came from. A card still as the release has it gets a warning: an edit, of its content or its wrong options, detaches it, so a newer release no longer updates it (it may still retire it). A card already changed says that newer releases no longer update it (migrations.md).

Solid Memo's own card page edits the wrong options too, under the card's fields. There they are saved with the card, by its Save. Left as they were, they follow the card as it is read afresh (a change in the inspector meanwhile included), and the save does not state them, so the card keeps its own.

While the deck is set aside, or the data check is under way, every tab is shown but nothing in it can be changed (below).

Edits not saved. The content tab's card editor says while it holds changes not saved, since it opened or was last saved. Leaving them then (another card by j, k, Previous, Next or a row of the list, Close, another tab, any other link or key that goes to another page, or the browser's Back and Forward, or a hash typed in) first asks, in a dialog (shell/ConfirmDialog.tsx, on the Studio's Dialog): Save saves them as Save does, then goes on, unless the save failed or was refused, which the editor shows; Discard goes on without them, and a cell that stays on the page shows them no longer; Stay, Escape or Close goes nowhere (shell/unsavedEdits.tsx). A table's cell edited and not saved (Layout) is asked about the same way, beside a card's edits: Save saves them all, and goes on only once each is saved. Back or Forward is undone before the question, before the Studio's router hears of it: the browser goes as many entries the other way (the router numbers the history's entries, historyPosition in routerCore.ts), and once the question is answered so, as many again, where Back or Forward went, the entries after it kept for Forward; a hash typed in, a new entry, is undone as Back is. A card's edits are never saved unasked. Leaving the app (a reload, closing the tab) meanwhile is asked by the browser itself (beforeunload). The edits are kept as the window crosses 64rem, the card going from beside its deck's cards to alone, or back: the editor starts again from what the one before held (keep, in CardScreen), until another card or tab is shown.

Opened at a field (field, as the health screen links to one), the inspector puts the focus there: on the text, or on the wrong option's Edit button (data-arrival, as useScreenFocus in ui takes it). Another tab opens at none.

The inspector reads the deck's cards with the same query as the workbench and Solid Memo, so an edit in one shows in the others. The schedule tab reads the review states with the workbench's query too.

Review state

The domain makes both changes (reviewStateEdits.ts):

The states are those Solid Memo reads (data-model.md): joined to their card by sm:reviewOf and sm:reviewDirection, the fragment rule only where a state leaves them out, so a state another app named its own way is edited in place. Only sm:sm2 states are touched.

Neither touches the answer log: the card's answers stay in its history and in the statistics. The use cases resetCards and rescheduleCards read the deck's review states, write the change in one write of the reviews document (none when no card has a state), and bring the deck's schedule in the digest up to date. The screens then read the review states afresh and drop the deck's study queue. The end-to-end tests hold both against real servers (reviewStateEdits.integration.test.ts).

Moving and copying cards

Move to deck… and Copy to deck… open a form (TransferCardsDialog). It offers the instance's other decks, and Keep their progress, checked to start with. It says what becomes of the cards' history. A moved card leaves the selection.

The domain plans the transfer (cardTransfer.ts, planCardTransfer):

The use case transferCards writes the target first: its cards document in one write, made only if it is still as read (If-Match), then its reviews document. For a move, it then writes the source's reviews document, and last its cards document, If-Match too. A document changed meanwhile, or made since it was read as absent, has the whole transfer planned again, three times in all, then it stops with "changed elsewhere". Then the schedules of the decks it touched are brought up to date in the digest. The workbench reads both decks afresh, and the instance's decks for their counts.

A move stopped half way (the target written, the source not, or only its review states) leaves the cards in both decks, never in neither. Making it again finishes it. The plan names the same ids each time, and a card the target holds already under its id, saying the same, is not written again; a state it has there is kept. Two cards of the source saying the same are never both found at one id. The source's states go before its cards: a card is never removed while its states stay behind, where nothing would clean them up. The end-to-end tests hold this against real servers, a move stopped before the source's cards were written included (cardTransfer.integration.test.ts).

A deck's schedule

A deck's schedule (DeckInsightContainer) shows what its study will be. The domain computes it (scheduleInsight.ts, cardHistory.ts), and the use case deckInsight reads what it needs. It writes nothing to the deck.

The charts are the statistics' own (BarChart and StatTile in ui): a bar pointed at, tapped or reached with the arrow keys says what it holds, and each chart has its figures as a table.

A deck's about screen

The about screen (DeckAboutContainer) shows what a deck says of itself and how it is studied, with a link to its page in Solid Memo. Each part has its own form, and a status line says when a save is made:

After a save the decks are read afresh, and the deck's study queue is dropped, as Solid Memo does after a change of its pace.

The instance

The instance's screen (InstanceAboutContainer) shows where the instance is, and links to its study preferences in Solid Memo. Its forms:

Both forms are held until the data check is done, and while the catalog or a group is set aside (below).

The meta document and catalog writes pass the shape check. The type index renames have no shape, so they are If-Match writes only. Each write is made again from a fresh read on a 412, three times in all. The end-to-end tests hold these edits against real servers (metadata.integration.test.ts).

Health

The health screen (HealthContainer) shows everything wrong with a deck, or with the instance. It is there for every user, not only in developer mode. "Check again" reads it all afresh.

A deck's health is the use case checkDeck. The domain puts it together (deckHealth.ts). A line counts the problems, then the screen has four parts:

Each counts toward the badge on Home: every violation (warnings aside), every side to settle, every group of cards that say the same, and every Markdown finding (healthProblemCount).

While the deck is held (set aside, or the instance blocked: below), its forms change nothing, so the screen does not send the user there. Every place is named, not linked, and a line says why. The repairs and removals stay: they are the way out. A problem no repair covers is then removed here, or corrected in the pod by other means. What the deck's check finds (as its screen opens, after a repair, or on "Check again") takes the place of what the instance's check found of that deck (withDeckReport), in the same place in its list, and that is what holds it. So a deck mended either way is let go without a reload, and without the whole instance being checked again. A check overtaken by another, such as one still running when a repair is made, is dropped: it read the pod before the repair.

The instance's health is the check made when it is opened (checkInstance, the same query as Home's), with its repairs, then every deck with its badge, a link to its own health. A result about a deck (its entry, its distribution, a subject of its documents) links to that deck's health. A badge checks its deck once: a repair or a removal here reads every check of the instance afresh (their queries are under its key); on a deck's health, only the deck's. The deck's health is read afresh each time its screen opens. What a badge's check finds holds its deck too, as above. The end-to-end tests hold checkDeck against real servers, the documents it checks again and a removal that clears it included (deckHealth.integration.test.ts).

Library copies

The library copies screen (LibraryCopiesContainer) lists the instance's decks copied from a release (they have prov:wasDerivedFrom), from the library or added from a link, a row each: the release it came from, the newest one out, and what upgrading would change. It links to Solid Memo's deck library.

The use case listLibraryUpdates reads the instance's decks and the library's index once, however many copies of a deck there are. The domain puts them together (library.ts, libraryCopiesOf): a copy of the current release, or of one the index lists as no older, is up to date (offersNewerRelease, as Solid Memo's offer tells it). A copy whose deck the index no longer lists says so. A release the index does not list is an unknown release.

For each copy with a newer release, the screen asks what upgrading would do (planLibraryUpgrade, the plan Solid Memo's deck page offers; migrations.md). It gives the copy as the copies were found, its series with it, so the index is not read again for each copy, nor a link's catalog. It says so in a sentence, as Solid Memo does. A newer release with nothing for the copy says so.

The table is a DataTable: j and k move its cursor, and the copy at the cursor is shown in the aside (named Upgrade, Layout), with the rest of its plan: what history is kept, the new releases' notes, and the cards added, changed, retired, brought back, removed, and left as the user has them. Which copy that is lives in the page, not in the URL; on another screen and back, none is shown until the cursor moves.

The copies that can be upgraded are selected by their checkbox, or all at once; a copy set aside cannot be, nor any until the data check is done (below). Update 2 decks (as many as are selected), in the selection's bar (Layout), upgrades them one after another, each with Solid Memo's own upgrade (applyLibraryUpgrade), on the plan the user saw: a deck changed since is refused, and left as it was. Nothing about an upgrade is new here. While it runs, the screen names the deck, "2 of 3", and its steps. Then a line for each deck says the release it is at now, or the step it failed at and whether the deck changed: one cut off part-way can be studied as it is, and updating it again finishes it (migrations.md). A failure is that deck's alone; the next is upgraded all the same. Everything an upgrade touched is read afresh, the copies among it, and so is a failed deck the upgrade changed in part. A failed deck is planned again too: it may have changed in another tab. Each upgrade still reads the index once more, to plan again with the deck as it now is. The end-to-end tests hold listLibraryUpdates and a batch of two upgrades against real servers (deckUpgrade.integration.test.ts).

A copy whose series the index does not list is looked up as a copy of a release added from a link (deck-library.md). Its release is read, and its row says "From <host>". Its creator's catalog, found in the folders above the release and read as anyone reads it, lists the releases its instance published (sm:publishedRelease): the newest of the copy's series is its upgrade, planned and applied as a library copy's. Each release a catalog links is read once for all the copies, and none the copy's own release lists, which is no newer. Before an upgrade is planned, the newer release and those in between are checked against the library's shapes, as the first was; one that breaks them is refused (releaseNotConforming). When no public catalog of its creator lists the release, it is unknown whether a newer version is out, and the row says "Not known: no public catalog of its creator lists it". A catalog can be private (an instance's is, unless its owner shares it), or not link the release. A copy whose release cannot be read either shows as no longer in the library.

Import and export

Import and export (TransferContainer) saves decks as files and makes decks of files. A file is Turtle or JSON-LD, one deck to a file, as data-model.md describes it.

Export lists the instance's decks, as they are arranged. The decks checked are in the URL, so Home's bulk Export and a deck's Export tab open the screen with them checked. The user picks the format and whether their progress goes too: the cards' review states and a course's completed chapters. Export saves each deck as a file of its own, named after its title (capitals.ttl), one after another; the screen names the deck it is at. The use case exportDeckFile reads the deck's documents (DeckArchive.exportDeck) and hands the text to the browser to save as a download (FileExchange.save). It writes nothing to the pod. A browser lets a page start one download by itself; for more, Chrome and others ask the user to allow several downloads, and save none of the rest if they say no. The app cannot tell, so after several decks the screen says how many it handed the browser, not how many were saved, and that the browser may ask.

Import asks for a file (openDeckFile, FileExchange.open): a .ttl, .jsonld or .json file. Its format goes by its name, else by its text. The screen then says what the file holds: its deck, its cards, its progress, how many of its parts were in an older format and are brought up to date, and how many cannot be read and are left out. A file in a newer format than the app reads is refused, as is one without exactly one deck, or one that does not parse. Import into the instance (importDeckFile) makes a new deck of it, with its progress when the file has some and the user leaves it checked. Its cards document goes first, only if none is at its URL yet, then its review states, then its entry in the catalog, so the deck shows only once it is whole. One left behind by an import cut off makes the deck take a fresh id, once. The deck's schedule in the digest is then brought up to date, and the screen links to the deck's cards. Every query of the instance's decks is read afresh.

The deck is written as any deck Solid Memo writes: its wrong options named with sm:distractor and schema:suggestedAnswer, its states naming their card in the new deck. It needs no type-index registration of its own: the instance's registrations of its decks, cards and review states cover it (data-model.md). While the data check is under way, or the catalog is set aside, the import is held (below); a file can still be read.

A file in a newer format is refused as deckFileTooNew, not as writtenByNewerApp: that one refuses to save over pod data a newer version wrote, while here nothing is saved over, and the file is what the app cannot read.

Each write passes the shape check. Turtle and JSON-LD go through @inrupt/solid-client, and files through the browser's own Blob and file input: no new library (boundaries.md). Unit tests take a deck with Markdown, pictures, wrong options (one retired), text in two languages, a retired card and review states through both formats and back (solidDeckArchive.test.ts), and the end-to-end tests do so against real servers (deckFiles.integration.test.ts).

Drafts

A creator writes a deck or a course as a draft of a release, in the instance, before publishing it as a release others can add. The drafts screen (DraftsContainer) lists the instance's drafts, each with its kind (deck or course), the version it drafts and whether it is being written, was released, or cannot be read; one is deleted, with its documents, once the user confirms. Where and how a draft is kept is in data-model.md; it is private, in the private type index alone.

Each draft that can be read links to its overview, on this screen and in the sidebar. A draft made opens at its overview at once, the activity saying it was made; but a draft of a deck copied from a release stays on this screen, which offers to start that release's next version instead (below).

A new draft starts from

A release is made a draft only if its version is 1, 2, … and every id in it is one a draft can keep: letters A to Z, digits, ., _ and -, starting with a letter or digit (unsupportedIdOf). A chapter is kept in a document named after its id, which another id would not survive; such a release is refused (releaseIdUnsupported) before anything is written.

It is named after its title, or its release's name, unlike the instance's other drafts of that version. Only a draft's place taken meanwhile (its release document made there first) gives it the next name; any other failure, such as the catalog's link refused on 412 a few times, takes back what was written and is thrown, so no draft is left that the catalog does not link. The use cases are listReleaseDrafts, createReleaseDraft, copyReleaseDraft (a guest's draft, as the guest's study is added to an instance, guest-mode.md), getReleaseDraft, editReleaseDraft and deleteReleaseDraft (releaseDrafts.ts), over the ReleaseDraftRepository port. When a document changed elsewhere meanwhile, the changes are made again on the draft as it is now; if they are then refused because part of them was written before (a chapter they add is there), what they changed is kept where nothing else changed it (mergedDraft). What a release before a draft published is read from that release (dcat:prev), once, since a release never changes; while it cannot be read, everything the draft has counts as published, so nothing is deleted that might have been. Making or deleting a draft changes the catalog, so both are held until the data check is done, and while the catalog is set aside (below). A draft made with a deleted draft's name has its URL, so what the screens read of a draft is forgotten when one is deleted or made.

A draft changes by DraftChanges (releaseDraft.ts, UseCases.editReleaseDraft): what the release says of itself, its agents, chapters and steps (added, edited, moved, retired, restored or deleted, the chapters and steps in use numbered 0, 1, … again), cards and their wrong options, where a question is asked, its sources, and the checks it had. A change is refused, writing nothing, when it would break what a release promises: deleting what an earlier release published (it is retired instead), giving an id that is another subject's or was an earlier release's, asking a card from a second place, a course not studied front to back, or rewriting how an earlier release was made. No change touches what an activity carried from an earlier release states: one that would (removing an agent it names) is refused. A check informs, and a source is used by, the activity of the draft's own that generated the release; a next version, generated only by activities carried over, is given one when it first needs it, #revision-<N>. A check is recorded only as a machine's or an AI's, in words that say it was no human review ("Scope: …; an AI check, not a human review."). The release screen makes these changes (below). A creator names the ids, the way courses.md says.

Writing a draft

A draft is written in six screens: its overview, a chapter, a step, a question, its cards, and its release metadata and provenance. They share the draft as one query, read once (getReleaseDraft), and change it by DraftChanges (below). Four more read it: its release check, its listing preview, its trial, and its comparison with the release it follows.

How edits are saved

Every edit is made at once on the screen, then written in the pod (editReleaseDraft), by the editor's hook (draftEditor.ts, useDraftEditor):

A draft released is frozen: every screen shows it, and changes nothing in it. Its next version is started from the drafts, its release screen or the releases (below). While the instance's data check is under way, or blocks the instance, nothing is changed either (below).

A draft's overview

The overview (DraftOverviewContainer) names the draft, its kind and version. Its title and description are saved as they are typed. For a course, it shows the outline, adds a chapter (its title, and its id), and lists the chapters retired, to restore. It counts the draft's cards, with a link to their table; a deck's cards are added here. It counts the problems the release check finds (for a pod, the shapes left out); the draft's tabs go to the check itself, the listing preview and the release's metadata and provenance.

The outline

The outline (DraftOutline) is a course's chapters in their order, each with its steps ("Step 1.2"), each step with the questions that check it, in the order of their ids. Each links to its editor. The list is named Outline. Chapters and steps are arranged as the deck list's groups and decks are, with the same code in ui:

Each move is a moveChapter or moveStep change (outlineMove in draftOutline.ts). A drop the draft no longer has a place for (it changed under the drag) comes to nothing, and says so.

A chapter, step or question the release check finds problems in has a badge beside it ("2 problems"), a link to the check.

On a course draft's other screens the outline is in the aside (OutlineAside, above), to go from one chapter, step or question to another and to arrange them there too. The one the page shown edits is marked (aria-current="page"; currentOutlineKey in shell/workspaceLayout.ts), and each badge links to where its first problem is fixed, as the check's links do (targetRoute). The overview keeps the outline in its main content, with its new chapter and its retired ones, and has no aside: the outline is shown once.

A chapter

A chapter's screen (ChapterEditorContainer) edits its title, always plain text, and its description, with "Format with Markdown" (sm:textFormat of the chapter) and a preview as the course's screen shows it (beside the field on a screen 80rem wide or more, under it on a narrower one). It orders the chapter's steps, up or down, adds one, and restores one retired. It lists the questions asked only in the chapter's final review, and adds one.

A step

A step's screen (StepEditorContainer) edits its theory, in each language it has, with "Format with Markdown" (sm:textFormat of the step). In Markdown, each text is hinted at where it would not show as meant, by the rules of prose (PROSE, markdown.md). Beside it on a screen 80rem wide or more, and under it on a narrower one, a preview shows the theory as the course player does (ProseField in ui, with the player's DataProse). The screen lists the questions that check the step, and adds one: its front and back, its id, and the question it is asked before, if not after the last.

A question

A question's screen (QuestionEditorContainer) says where the card is asked: a step, a chapter's final review, or nowhere yet. Choosing another moves it there (moveQuestion). Its content is Solid Memo's card editor (CardContentFields), saved by Save. Its wrong options are the inspector's own (DistractorFields), each change saved as it is made (distractorChanges). A preview asks the card as the course will (CourseQuestion, with MultipleChoice), as typed: it can be answered, and asked again. On a screen 80rem wide or more it is beside the form, and stays in sight as the form scrolls; on a narrower one it is under it.

A draft's cards

A draft's cards (DraftCardsContainer) are a table, a row each, by id: its front (a link to its editor), its back, where it is asked, its wrong options in use, and its id. The URL holds the view (above). The filters keep the cards asked nowhere (in a course), asked from two places, with fewer than two wrong options in use, or retired; the language keeps those with text in it on their front or back. A column's name sorts by it, then the other way, then by id again, from the first page. A page holds 50. The table is a DataTable: cards are selected by their checkbox, or a page's at once, and Retire 2 cards or Restore 1 card, in the selection's bar (Layout), retires or restores those selected the filter keeps, in one edit; nothing is selected while the draft may not be changed. Enter opens a card's editor, as its link does. A new card is added under the table, asked nowhere yet.

Ids

The id assistant (courseIds.ts) suggests each new subject's id, the way courses.md names them: ch-<title> for a chapter, <chapter>-<n> for a step, q-<topic>-<n><letter> for a step's question, sorting after its last, and q-<topic>-r<nn> for a review question. The user may write another. The field says at once when an id is none a subject can have, or one the draft or a release before it has. A step asks its questions in the order of their ids, and a published id is never renamed, so a new question's id sorts after the step's last. Past z, it is the last's id with a number after it. A new question may instead be asked before one the step or review asks already ("Ask it", questionIdBefore): its id then sorts between that one and the one before it, a free letter when there is one, else an id from idBetween. Before the first, the place's own name (q-<topic>-<n>, q-<topic>-r00) is the lower end. When no id sorts there, the form says so, and the user writes one. An id the user writes is asked where it sorts: when that is not where "Ask it" says, the form says so too. idBetween finds an id that sorts between two others, by code unit, and is none taken: a number after the first when one fits, else one made code unit by code unit. A seeded property test checks that it keeps the order and never gives an id taken.

Nothing an earlier release published is deleted: a learner's progress may name it. Its Delete is held, and the screen offers to retire it instead. A wrong option it published is likewise only retired.

The release check

The release check (ReleaseCheckContainer) runs every rule a release is held to on the draft, as the release it will be. They are the rules npm run library:check runs on decks/ (deck-library.md), from one source: the domain's (release/). The use case is checkReleaseDraft(draft, markdownCheck, policy). It finds:

The library and drops parts read what is elsewhere: the library's index, and the release the draft follows. One that cannot be read is a problem of its own (libraryUnread, previousUnread), and the rest of the check stands. Such a check is not kept, so the next one reads again: the screen checks again when it is opened again, when its window is focused, or when asked: such a check, or one that failed, has a button to check the draft again.

The draft checked is the editor's, changes not yet written included, so a problem fixed clears at once. Each version of it is checked once: the editor makes a new draft for each change, and the use case keeps each part per draft. The release a draft follows is read once.

The policy is a pod's by default: a release in a pod is held to its data's rules only. policy=library adds the Solid Memo library's curation (repoPolicy: English and Swedish keywords, the theme EDUC) and its place among the library's decks. Neither names a series, author or host: a library is its index, and a policy is data.

Problems are codes, never text (problems.ts). The screen words each in the reader's language (studio.problem.<code>, problemText.ts), by severity, errors first, then by what it is in: a list each, named Errors and Warnings after its heading. Each subject links to its editor, and each problem to its field there (problemTarget): a wrong option's problem opens its question at it. The editor opens with that field focused. The outline's badges count a wrong option's problems under its question.

Not every problem can be fixed in the Studio yet:

So a new draft's problem count may not reach zero.

The listing preview

The listing preview (ListingPreviewContainer) shows the draft as learners will see it in the library: its row in the library's list (LibraryDeckRow) and its own page (LibraryDeckScreen), Solid Memo's own components, over a library deck built from the draft alone (draftLibraryDeck in draftListing.ts). Both are inert: a picture, nothing in it to follow or press.

The trial

The trial (TrialContainer) plays the draft as learners will play the release it will be: a course with Solid Memo's own course screens (its page, a chapter's steps, its final review), a deck with Solid Memo's study. It is the draft's Trial tab.

It is played in a sandbox, so nothing done in it reaches the user's pod. The use case is openTrial(draft, instanceUrl) (useCases.ts), which sets the sandbox up with startTrial (trial.ts):

A draft that cannot be played is refused, with what keeps it from it (trialProblems), each a link to its field, as in the release check: the course's rules, and what a release needs that the player needs too (its title, and each chapter's and step's fields). What a release says of itself besides its title (its publisher, version, series, distribution and theme) does not keep a draft from being played.

The course's screens link to the trial's routes, not Solid Memo's: CourseLinks in ui (courseLinks.ts) holds where a course's page and its final review link to a chapter, Solid Memo's course routes unless a provider says otherwise. Each trial has its own query client too, so nothing it reads mixes with the user's.

Its controls (TrialControls):

After each control, what is shown is read again and starts afresh. A trial lasts while its screen is open: leaving it, or reloading the page, forgets it.

The release diff

The release diff (ReleaseDiffContainer) compares a draft with the release it follows (its dcat:prev). It is the draft's Changes tab, which a draft has when there is one. A draft of a first version follows none, and the screen (still at its address) says so. The use case is diffReleaseDraft(draft) (releaseDrafts.ts). It reads the release once, as the release check does, and fails when it cannot be read. It shows:

The learner's copy is built in memory from the release and the draft as libraries are (draftLibraryContent in draftListing.ts): nothing is read or written for it. Each version of the draft is compared once, as the editor keeps it, changes not yet written included.

The release's metadata and provenance

The release screen (ReleaseMetadataContainer) edits what a draft's release says of itself beyond its listing, who made it and how, and what it was made from. It is the draft's Release tab. It writes the release as the library's releases are written (deck-library.md). Each change is a DraftChange, saved as the other screens save theirs (above). Its parts:

What the check asks of a release's provenance

The release check warns of what a reader of how the release was made would miss (provenanceProblems in provenanceRules.ts). Each is a warning, never an error, and links to the release screen:

The library's command reads a release alone, and cannot tell which of its making an earlier version's is, so it does not run these rules: the Studio runs them on a draft, which knows (quadsToReleaseModel marks no making carried).

Publishing a release

Below a draft's metadata, the release screen (ReleasePublishContainer) releases it, two ways.

Publish to my Pod (publishRelease in releasePublishing.ts):

A pod that will not make the release public (it has no access control the app can write, it refuses the write, or it names the folder's access control as the release's, so the write would change the whole folder's) still has the release, written and listed, readable by its owner alone. The draft is released all the same: what is written there is frozen. The screen says so ("Only you can read it"), with Make it public, which tries the access again (makeReleasePublic; publicAccessRefused when it still fails). Whether a release is public is asked as someone with no login would ask (isReleasePublic, a request without credentials).

A guest's draft is not published: a guest's pod is in this browser, and no one else could read it. The screen offers the download alone.

A released draft shows where its release is, a link to copy, whether anyone can read it, and Start the next version: a new draft of version N + 1, which keeps everything the release published (above), and opens. A released draft can also be deleted from the drafts; the release stays.

Download .ttl (downloadRelease) saves the draft as a release file, <name>-v<N>.ttl, at the address the library would publish it at (libraryReleaseUrl, by the library's index), to propose it for the library. Nothing in the pod changes, and nothing is checked: the library's own check does that. The screen lists the steps of a pull request, in a copy of the repository:

  1. Put the file in decks/ as decks/<name>/v<N>.ttl.
  2. npm run format:turtle && npm run library.
  3. npm run library:check -- --base main.
  4. npm run check && npm run crosscheck.
  5. Open a pull request with the new file and the index.

It links to the draft's release check for the library first (above).

Releases

The releases screen (ReleasesContainer, listPublishedReleases) lists the releases the instance published, as its catalog links them. Each has its address, named by it ("capitals, version 2") when it is named as releases are, a link to copy, who can read it (everyone, or only its owner, with Make it public), and Start the next version, which makes a draft of it and opens it. Each links the draft it was published from (the draft says what it was released as), while the instance has it. It changes no release. The table is a DataTable: a column's name sorts it, then the other way, then as the catalog lists them, Enter opens the release at the cursor, as its name's link does, and a filter keeps the releases whose name or address holds its text, case and accents aside. The sidebar and the drafts screen link to it ("Published releases" there). A learner adds a release in Solid Memo by the link copied here (deck-library.md).

Data set aside

Every Studio screen holds to the check Solid Memo makes when an instance is opened, and to the user's invalid data policy (validation.md). useDataCheck in ui reads both, with the same queries as Solid Memo's workspace, so the check is made once and again after a repair. A deck checked again on its own (its health) puts what that check found in its place:

A held form is a disabled fieldset (ReadOnlyScope), so no control in it can be used, while links still lead on; what is typed in it stays. The health screen's repairs are never held: they are the way out. It does not link a held deck's problems to its forms (health).

Groups

Groups (GroupsContainer) is Solid Memo's deck list without the study: the same screen (DeckListScreen) and the same edits, kept by useDeckTreeEditor in ui, which Solid Memo's DeckListContainer uses too. Decks and groups are dragged, moved from their menus, grouped, renamed and deleted there. What is not arranging (a deck's page, its preferences, a course, a new deck, the library) opens in Solid Memo. As in Solid Memo, a deck set aside says so, and nothing is rearranged until the data check is done, nor while the arrangement is set aside. Unlike Solid Memo, a deck held (above) cannot be renamed or deleted either (deckHeld), and a line says why, with a link to the instance's health.

How it is built and served