Deck library
The deck library is the set of ready-made decks and
courses anyone can copy into their own instance. They are files in
this repository's decks/ folder, published with the site and
described in DCAT, a standard catalog format other tools can read.
This page says how a deck version is written, checked and published,
and how the app lists, imports and adds one from a link.
Ready-made decks that any user can copy into their own instance. The
library lives in this repository's decks/ folder and is
published with the site, next to the app
(deployment.md), at https://solid-memo.com/decks/.
It is described with DCAT and
conforms to DCAT-AP: the
library is a dcat:Catalog, each deck a dcat:DatasetSeries of its
versions, each version a dcat:Dataset with its cards and a Turtle
dcat:Distribution.
Path under decks/ | IRI | What |
|---|---|---|
index.ttl | https://solid-memo.com/decks/index.ttl | The catalog the app lists, generated from the versions and committed: each deck a series (index.ttl#<name>), its current version described in full but for its cards, how it was made and any email, the publisher index.ttl#solid-memo, and the course for newcomers. |
<name>/v<N>.ttl | https://solid-memo.com/decks/<name>/v<N>.ttl | Version N of a deck, frozen once published; its cards are fragments of it (…/v1.ttl#sweden). |
Nothing else is in decks/: no lock file, no sources, no scripts. A
deck's name is lower-case letters, digits and dashes; its versions run
v1.ttl, v2.ttl, … without gaps. Every IRI ends in .ttl, as the
vocabulary's and the shapes' do: GitHub Pages serves a .ttl file as
text/turtle but negotiates no content, so an address without the
extension would be a 404. Each file states its own address as its
@base.
The diagram's source
flowchart LR
accTitle: Publishing a deck version
accDescr: A new version is copied from the frozen one, indexed with npm run library, checked with npm run library:check and deployed with the site.
prev["decks/name/vN.ttl<br/>(published, frozen)"] -->|copy, edit| next["decks/name/vN+1.ttl"]
next -->|npm run library| idx["decks/index.ttl<br/>dcat:Catalog"]
next & idx -->|"npm run library:check<br/>-- --base ref"| ok["valid, index up to date,<br/>nothing published changed"]
ok -->|deploy| site["solid-memo.com/decks/…"]A version
A version is one sm:Deck (also a dcat:Dataset), the document
itself (<>), in library deck format 5 (LibraryDeckV5,
shapes.md) or, from vocabulary 1.18, format 6
(LibraryDeckV6, the same properties), with its cards as hash-fragment
subjects in card format 4, 5 or 6. A version stays at the format it was
published in; a new one is written at format 6, its series and
publisher still in the index:
- Title and description are language-tagged, one per language, one
of them English (the library's curation policy, so that anyone who
reads English can read every deck). Keywords are tagged too,
several per language. Title, description and keywords come in every
language the app speaks (English, Swedish, Korean, German, Spanish and
French): when the app gains a language, every deck gets a version that
adds it. The app shows only the reader's keywords
(i18n.md). Topics are
dcat:themes fromns/vocab/topics.ttl, next to the EU data themeEDUC; languages are EU authority-table IRIs described inns/vocab/external.ttl. - Creators are
foaf:Agentnodes of the document, with a name and no email; licenses are IRIs typeddcterms:LicenseDocument, and a version states at least one (Checks); the sources it was compiled from areprov:wasDerivedFromIRIs, each a subject of its own with its title, creator and license. - The contact is the project's issue tracker, not a person's email (below).
- The release:
dcat:version "N",dcterms:issued, oneadms:versionNotes(a plain string: the shape allows one, untagged),dcat:inSeriesanddcat:isVersionOf<…/decks/index.ttl#<name>>,dcterms:publisher <…/decks/index.ttl#solid-memo>, adcat:distribution <#turtle>pointing at the file itself, and from version 2dcat:prevanddcat:previousVersion<…/<name>/v<N-1>.ttl>. - A published card is never removed, but retired: keep it and add
owl:deprecated true(card format 3 and later). Copies of the deck keep it and its review history, but no longer study it; a later version can bring it back by leaving the flag out.sm:cardCountin the index counts the cards in use. - Text in a language written right to left is tagged with its
language like any other (
"كِتَاب"@ar), with no bidi control characters: the app lays it out right to left from its tag (i18n.md). The Arabic decks write their Arabic fully voweled and in NFC, which puts a vowel mark before a shadda on the same letter, whatever order it was typed in.
decks/greek-alphabet/v1.ttl is a
small, complete example.
Courses
A version may be a course: a deck whose cards are taught in chapters of steps, each card asked as a multiple-choice question. Beside what every version has, a course has:
- the extra type
schema:Courseon the release itself, andsm:studyDirection sm:frontToBack; - its chapters (
sm:Chapter, chapter format 1) and steps (sm:Step, step format 1), subjects of the release beside its cards; - on each card it asks,
sm:distractorlinks to its wrong options,sm:Distractorsubjects (distractor format 1) of the release.
The index leaves the chapters, steps and distractors out, as it leaves
the cards out. It keeps the release's schema:Course type, so the app
can tell a course from a deck by the index alone (LibraryDeck.isCourse).
The app reads the outline from the release when the learner opens the
course. decks/solid-fundamentals/v1.ttl
is the first course, and the first release written in Markdown.
A course's chapters, steps and distractors are never removed, as its
cards are not: one that should go is retired (owl:deprecated true), so
the learners' decks that follow the course keep their place in it.
The index names at most one course for newcomers:
<> sm:newcomerCourse <#name> on the catalog, the series of a course
the app offers to someone with no decks yet
(courses.md). It is library
configuration, not part of any release: NEWCOMER_COURSE in
deckLibrary.ts names the deck
(io.newcomerCourse in main, so its tests can name another), and
undefined names none (and retires the journey that expects an offer,
courses.md). The term belongs to no shape: CatalogV1 does
not own it, and DCAT-AP ignores it.
Contact
A version names the library's contact, the project's issue tracker, as
its dcat:contactPoint: a vcard:Kind node of the document, as
DCAT-AP 3.0.1
describes one. It names no one's email: no subject states foaf:mbox.
@prefix vcard: <http://www.w3.org/2006/vcard/ns#> .
<> dcat:contactPoint <#contact> .
<#contact>
a vcard:Kind ;
vcard:hasURL <https://github.com/solid-memo/website/issues> .
The versions published before this rule name the maintainer's email
instead (foaf:mbox on <#anton-wiklund>). They are frozen, so they
keep it; a deck's next version drops it. The index never repeats an
email, whichever version is current: keptInIndex
(seriesEntry.ts) leaves
out every foaf:mbox, and keeps the current version's contact. The
command checks the rule on every version new since --base
(contactProblems, Checks); CONTACT_URL in
deckLibrary.ts names the
tracker. A release in a pod (studio.md) is the author's
own and may name an email.
Provenance
Each authored deck says how it was made, in PROV-O, inside its version file:
- The deck is
prov:wasGeneratedBy <#compilation>, aprov:Activitywith when it ended, the sources itprov:usedandprov:wasAssociatedWith <#anton-wiklund>. Its first comments are the attribution, "Compiled by Anton Wiklund with the help of AI."@en and "Sammanställd av Anton Wiklund med hjälp av AI."@sv; the others say how the cards were selected, researched and checked, and on what grounds the deck may carry its license. - The compilation
prov:wasInformedByits quality-control rounds,<#review-0>,<#review-1>, …: each aprov:Activitywhose label names what it looked at (sources and licenses, facts, language, …) and whose comments give its scope and its outcome. Machine checks (the SHACL and DCAT-AP validators, checks against live Wikidata) are named as such. - Each source has an
rdfs:commentquoting the evidence for its license, and says whether the deck took content from it or used it to verify facts only.
The record is condensed from the working notes kept while a deck was
compiled; those notes' per-card evidence and queries are not part of
the library. The few decks that were not researched card by card
(generated from one source, such as the Swedish word decks and network
ports, or written directly, such as the capitals) describe their sources
and licenses, some the command that generated them, but have no review
rounds. The index leaves the
record out (every rdfs:comment, and the activities but for the
compilation's type, which DCAT-AP asks for); the app shows the deck's
sources and license on its page, and the version file has the rest.
Publishing a new version
A published version is never edited or removed, only followed by the
next. To change a deck <name> whose latest version is N:
- Copy
decks/<name>/v<N>.ttltodecks/<name>/v<N+1>.ttland edit the copy: cards (retire, never delete), texts, sources, provenance. - In the copy, change its
@baseto its own address (…/<name>/v<N+1>.ttl) and setdcat:version "<N+1>",dcat:prevanddcat:previousVersion<https://solid-memo.com/decks/<name>/v<N>.ttl>,dcterms:issuedto the release time, anddcterms:modifiedif the deck has it,adms:versionNotesto what changed, in one string,- the contact:
dcat:contactPoint <#contact>and itsvcard:Kindnode, and nofoaf:mbox(an agent copied from a version published before the rule has one: remove it).
npm run format:turtle, thennpm run library, which validates the library and writesdecks/index.ttl(the new version becomes the series'dcat:lastand current version). Thennpm run library:check -- --base origin/main, which checks the contact of every version new sinceorigin/main.- Look at it:
npm run devserves the working tree'sdecks/, so the library screen lists the new version and offers it to copies of the old one (migrations.md). - Commit the new version and the index together, as a
feat(decks)commit (CONTRIBUTING.md). The release it makes publishes them, once deployed (deployment.md).
A new deck is the same with decks/<name>/v1.ttl, without dcat:prev
and dcat:previousVersion, its notes "First release.". It is also
listed in REUSE.toml under the license its
dcterms:license names, as "decks/<name>/*.ttl", and a version that
changes the deck's license moves it there to the license's own
annotation, or names that version alone. npm run check:reuse fails
until it agrees (style.md).
Text over several lines. npm run format:turtle writes a literal
whose text has a line feed as a long string, """…""" with its language
tag, its lines verbatim and never indented (leading spaces can matter).
A backslash and every control character but a tab or a line feed are
escaped (a carriage return as \r), and so is any quote that would end
or run into a delimiter, so the text reads back the same. Every other
literal stays on one line.
Authoring Markdown
A card, a course step or a chapter may be written in Markdown (markdown.md): CommonMark with pipe tables, for code, tables and lists. Text is plain unless its subject says otherwise, so a release that says nothing reads exactly as before.
- The marker. State
solid-memo:textFormat solid-memo:markdownon each card, step and chapter written in it, and on nothing else (vocab.md). On a card it covers its sides, notes and labels, and its distractors' text and notes, which carry no marker of their own; on a step its theory; on a chapter its description, never its title. A picture's description, a deck's title, description and keywords, and the version notes are always plain. - One marker per subject, not per deck. A card copied alone keeps its meaning. Mark only what needs Markdown: a card whose text is plain stays unmarked.
- Several lines are a Turtle long string,
"""…"""@en, whichnpm run format:turtlewrites with its lines verbatim and never indented, as a code block needs (above). - Each language is its own document. A code block in
@entext stays@en; a back that is code alone may be untagged or@zxx, its distractors' text then the same. - A step's theory may be in chunks, read one at a time: a
thematic break (
---) at the top level, with a blank line before and after it, ends one and begins the next (courses.md). Anywhere else a break shows as a rule. - Plain-text habits read otherwise in Markdown:
<url>is raw HTML,åis "å",*and_may emphasize,<ex:title>is a link. Write such text as code (`git clone <url>`). - A published version is never marked afterward. A deck takes Markdown in its next version; the library upgrade brings the marker to the copies (migrations.md).
<#q-staged> a solid-memo:Card ;
solid-memo:formatVersion 5 ;
solid-memo:textFormat solid-memo:markdown ;
solid-memo:front "Which Git command shows **staged** changes?"@en ;
solid-memo:back "`git diff --staged`"@zxx ;
solid-memo:backNote """Without `--staged`, `git diff` compares the working tree with the index:
| Command | Compares |
|---|---|
| `git diff` | working tree and index |
| `git diff --staged` | index and `HEAD` |"""@en .
npm run library checks what is written in Markdown by the
Markdown rules.
Checks
packages/shacl/node/deckLibrary.ts
is both commands. npm run library writes the index;
npm run library:check writes nothing and fails when the index is not
what the versions make. Both check:
- the layout: nothing but
index.ttland<name>/v<N>.ttl, the names plain, the versions running from 1 without gaps; - each version's metadata against its path:
@base, the one deck being the document itself,dcat:version, series, publisher, anddcat:prev/dcat:previousVersionnaming the version before (none for version 1); - that each version states its license (
noLicense): at least onedcterms:licenseIRI, so a reader knows how the deck may be reused. The shapes allow a deck without one, and a published shape is never edited, so this is a library rule, inmetadataProblems(libraryRules.ts). Every published version states one, so the rule holds new versions to what the library already does. The Studio's check for the library names it too; - that no version drops a card, chapter, step or distractor of the one before it, or gives one's id to another kind of subject (a step's id to a chapter);
- a course's outline, by the course rules;
- the index's course for newcomers (
newcomerProblems): at most one, an IRI, a deck of the library (one of the catalog'sdcat:datasets), whose current release is aschema:Course.buildIndexwrites the name even when there is no such deck, so a typo inNEWCOMER_COURSEis reported, not dropped; - text in Markdown, by the Markdown rules;
- every literal of a version and of the index in Unicode normalization
form C, composed (
normalizationProblems): "é" not written as "e" and a combining accent, nor "사람" as five jamo, as text copied from a macOS file name or a PDF may be. Such text looks the same but is not the same text; the problem names the first character that composes differently, by its code points; - every version and the index against Solid Memo's shapes, DCAT-AP (a version with the index beside it) and SKOS, with the reference data.
Where the rules live
The rules that read a release's data live in the domain, in
packages/domain/src/release/, so
the Studio can run them in the browser as the command does:
| File | What |
|---|---|
releaseModel.ts | A release as plain data: its metadata, every card, chapter, step and distractor (retired ones too), how it was made. |
problems.ts | ReleaseProblem: a code, a severity, the subject and field it is in, and what it needs to be worded. No text. |
libraryRules.ts | The layout and each version's metadata against its place. |
continuityRules.ts | A version against the one before it. |
courseRules.ts | The course rules; and what a release needs that its draft may lack (readinessProblems), which the Studio's check adds. |
markdownFields.ts | Which fields are Markdown, and by which rule; the check itself is passed in, since the domain reads no Markdown. |
curationRules.ts | A LibraryPolicy: what a library asks beyond the data. repoPolicy asks a title and description in English, keywords in English and Swedish and the theme EDUC; podPolicy asks nothing. |
seriesEntry.ts | What the index says of a deck. |
releaseCheck.ts | The Studio's release check: the rules a policy runs, where a library publishes a release (libraryReleaseUrl, librarySeriesUrl, which this command uses too), and where each problem is fixed. |
draftModel.ts | A Studio draft as a release model, at its own address or another. |
The command builds each release's model from its quads
(quadsToReleaseModel.ts),
words each problem in English
(releaseMessages.ts), and
checks itself what only the text or the shapes can: @base, Turtle
syntax, the shapes and the profiles. It does not run the curation
rules: LibraryDeckV5 and LibraryDeckV6 state English and EDUC for this library. Nor
does it name a version that is not the one before it plus one
(versionNotNext): here the path fixes each version, and the metadata
check names one other than its path says. A golden test
(deckLibrary.golden.test.ts)
keeps what the rules say of published releases with faults put in.
Course rules
courseProblems (courseRules.ts) checks what the shapes cannot say
about a course. It runs on every version, and finds nothing in a plain
deck:
- A release with chapters or steps is typed
schema:Course, and a course has at least one chapter. It studiessm:frontToBack. - A chapter is part of the release it is in (
schema:isPartOf <>), and a step is part of a chapter of the release. - Chapters each have their own
schema:position, and so do the steps of one chapter. - Each card named by
sm:checkedByorsm:reviewQuestionis a card of the release, and not a retired one. A retired step or chapter may still name a retired card, so a card can be retired without editing the step that checked it. - A card is checked by one step at most, and is either checked by a step or a review question of a chapter, never both.
- A card a step or chapter in use asks has text on its back (the right
option) and at least two distractors in use. Each of those has text in
every language of the back, exactly: an untagged back needs untagged
distractor text, a
zxxbackzxxtext. - A chapter in use has at least one step in use.
- Every
sm:distractorlink, in any release, names asm:Distractorof the release, and no two cards name the same one: a copy writes and removes a card's distractors with the card.
Retired steps and chapters do not count toward the rules on positions or on how a card is asked.
Markdown rules
markdownProblems (markdownFields.ts, with the markdown package's
check) checks, field by field, the text of every card, step and
chapter that states sm:textFormat sm:markdown, by the
rules for a release. In short:
- no raw HTML and no pictures;
- no link in a card's
front,back,frontLabelorbackLabel, or in adistractorText, an autolink (<ex:title>,<http://…>) included; elsewhere only links the app follows (https:, without a user name or password), whose text, when it reads as an address or a host name, names the very host the link leads to, give or take awww.(not a domain it is in, such asgithub.io). Any dotted word ending in letters reads as a host name: "Node.js", and file names such aspackage.json,v1.ttlorREADME.md, even as code, so word such link text otherwise; - no bidi control, zero-width or other hidden character in code or a link (docs/markdown.md lists them);
- no character reference (
å) outside code; - no heading underlined with dashes, a line of text with
---right under it, which looks like the text and a thematic break: leave a blank line before the break, or write the heading with##; - on a card with distractors, its
backand eachdistractorTextone paragraph, not a heading, as options are; - a step's theory with no empty chunk (a top-level thematic break first, last or right after another), and in as many chunks in each language, so a learner who switches language keeps their place;
- nothing past the limits the app reads Markdown within: 20,000 characters, 8 levels of blocks and markup (each quote, list item, paragraph, emphasis and link is one; plain text that shows as written anyway passes), a table of 20 columns or 2,000 cells.
It also checks that only a card, a step or a chapter states
sm:textFormat (a distractor's text is written as its card's, and any
other subject's is plain), and
only a concept of sm:TextFormats. A text not marked is not checked:
plain text is shown as written.
None of this is what keeps the app safe, which shows any text safely (markdown.md): it is that the text shows as its author meant.
With -- --base <git ref> they also compare with the library at that
commit: a version published there that is gone or differs by a byte
fails, and so does a version not published there that states
foaf:mbox or lacks the library's contact
(contactProblems). Without a base nothing tells a new version from a
published one, so that rule does not run. npm run check runs
library:check without a base, as a Turbo task of the shacl package
(turbo.json), cached until a file it
reads (ns/, decks/, its packages' code) changes. CI's build job
runs npm run check:ci, which leaves it out, since the library job
checks the same and more. CI's library
job (ci.yml,
testing.md) passes a base, on pull
requests, in the merge queue and on pushes to the default branch, not on
other branches' pushes, so a pull request's commits are checked once per
push; ci-ok waits for it like every other job. On a push to the default
branch it passes the commit the push moved it from; in the merge queue,
the commit of the default branch the queue built on
(testing.md); on a pull request, the
commit where the change left the default branch, so an edit to a published version is caught
before it is merged, while a version not merged yet can still be fixed.
A branch with no pull request is not compared; open one, or check it
locally before pushing:
npm run library:check -- --base origin/main
pySHACL checks the index and every version against DCAT-AP once more (validation.md), and libraryReleases.test.ts reads every version as the app does, its cards and a course's outline (testing.md).
Reading it
It is public, so the app reads it with a plain fetch, without logging
in. appUseCases.ts names
the index; during npm run dev and npm run preview the site's own
addresses are read from the local server (siteFetch), so the working
tree's library is what the app shows. VITE_LIBRARY_INDEX_URL names
another index at build time (configuration.md):
VITE_LIBRARY_INDEX_URL=https://example.org/decks/index.ttl npm run dev
The library conforms to the shapes in shapes.md:
CatalogV1, LibraryDeckSeriesV3, and LibraryDeckV5 or
LibraryDeckV6, each release against its own format's shape. The app
migrates a format-5 release to format 6 in memory.
A release outside the library
A release need not be in decks/: from library deck format 6, one
Turtle document anywhere (a release published in a pod) is whole
without an index. Beside the release and its cards, the document then
describes:
- its series, the subject
dcat:inSeriesanddcat:isVersionOfname: adcat:DatasetSeriesanddcat:Datasetin library deck series format 3, with its title, description, publisher, first, last, current and every version, as the index describes a deck; - its publisher, the
dcterms:publisherof the release and the series: afoaf:Agentwith its name; - each earlier version the series lists, as a
dcat:Datasetwith its title, description and version, so DCAT-AP finds every version described.
The same shapes check it (LibraryDeckV6, LibraryDeckSeriesV3,
AgentV1), and DCAT-AP accepts it with only the reference data beside
it (library-deck/v6/valid
is an example, which npm run crosscheck checks too). Nothing in the
data says who may publish one, and the app reads its content as any release's (toLibraryDeckContent).
Such a release is made from a draft in the creator's pod
(studio.md): every subject of the draft moved to the
release's address, @base that address, its release and change time
set, at library deck format 6. A series the draft starts (its own
<#series>) is described in it, the release its first, last and
current version; a series an earlier release described goes on being
described, the release added as its last and current version; a series
an index describes, as this library's, is left to the index. The next
version of such a release describes it, though: the draft copies from
the index its series, its publisher and its earlier versions
(readLinked, linkedDescription), so the release it makes is whole
too. So a release downloaded for decks/ from a next version carries
the index's statements of its series, publisher and earlier versions.
Only what a release needs is copied: of the series, what an index says
of one; of a publisher, its class and name. Every
release in decks/ made a draft and published again comes back as it
was, apart from what publishing sets (a test of its own,
testing.md).
In the app
The diagram's source
flowchart LR
accTitle: The library in the app
accDescr: The library screens and the newcomer offer call use cases that read the library with plain fetches and write imported decks to the pod with authenticated requests.
ui["LibraryContainer / LibraryScreen<br/>#/library?instance=…"] --> uc["listLibraryDecks<br/>importLibraryDeck"]
page["LibraryDeckContainer / LibraryDeckScreen<br/>#/library-deck?instance=…&deck=<series>"] --> uc
offer["NewcomerCourseContainer<br/>on an empty deck list"] --> start["listLibraryDecks<br/>startCourse"]
start --> lib
start --> repo
uc --> lib["DeckLibrary port<br/>(solidDeckLibrary.ts)"]
uc --> repo["DeckRepository.importDeck<br/>(solidDeckRepository.ts)"]
lib -->|plain fetch| idx["solid-memo.com/decks/index.ttl"]
lib -->|plain fetch| doc["solid-memo.com/decks/name/vN.ttl"]
repo -->|authenticated| pod["catalog.ttl + decks/deck-id.ttl"]toLibraryDecks(libraryMapper.ts) reads the catalog's datasets, each series' current release (through theLibraryDeckSeriesV3andLibraryDeckV6shapes, older formats migrated in memory) and its releases, showing the English title and description and keeping the other languages, which an import copies as they are (every tag, nothing guessed or added), and names creators from the agents in the index. An import writes the user's copy at the latest formats (deck format 6, card format 6), whatever format the release was frozen at: a release's keywords keep their tags, and a format-4 release's untagged keywords stay untagged, their language unknown. A series or release that does not fit its shape is left out.- The library screen lists each deck's name and card count with a
checkbox, filtered by topic (checkboxes of the topics the decks
name, a broader topic finding the narrower: "Languages" finds the
Swedish decks) and by a search of names, descriptions and keywords,
in every language (
filterLibraryDecksin domain/library.ts). The search ignores case and Unicode form: query and text are compared composed (NFC), so "사람" or "é" pasted decomposed (NFD), as macOS file names and some PDFs hold text, still finds the deck; and Arabic is compared without its vowel marks, so "كتاب" finds "كِتَاب" (matchesQueryin domain/search.ts,arabicFoldedin domain/langText.ts). The Browser's card search ignores every diacritic (folded), Arabic's the same way. Clicking a row opens the deck's page: description, topics, keywords in the reader's language, the release (version, date, notes), authors, license, dates and sources, an import button and "Browse cards" (a read-only, paged list fetched from the release document). - A deck's page is addressed by its series (
&deck=…/index.ttl#name), which outlives releases; an address of one of its releases still finds it. A deck the library does not have falls back to the library (routing.md). importDeckcopies the current release into the instance with one write of the cards document (all 243 cards of the capitals deck in a single PUT), then one write of the catalog, and carries over the description, authors (as agents), license, topics, keywords and direction. The copy says which release it came from withprov:wasDerivedFrom <…/decks/name/vN.ttl>(Deck.sourceUrl). Cards keep their library fragment ids (#sweden).- A pod deck is a copy of a library deck when its source is any version
of the deck's series (
isCopyOf); the library marks such decks "Already imported". A second copy is still allowed. - The copy is written in this app's own format, whatever the release said. A release or card in a newer format than the app writes is refused at import rather than silently stripped.
- Several decks can be checked and imported at once, one at a time; a failure part-way leaves the earlier ones imported and reports the error.
- A course is started rather than imported (the list offers no checkbox for it, only its page): its copy starts with no cards, and a card joins it when its question is answered (courses.md).
- The course the index names for newcomers is
LibraryDeck.forNewcomers(toLibraryDecksflags it only when it is a course). An instance with no decks is offered it under its deck list's heading, started in one click (courses.md). - When the library publishes a newer release of an imported deck, the deck's page offers to update the copy card by card, keeping what the user changed and their review history (migrations.md). The deck list says "New release" after the copy's name, so the offer is not missed by a learner who studies from the list.
From a link
A deck or a course published anywhere, a release outside the
library in its creator's pod say, is
added by its address: Add from a link, on the deck list and the
library, opens #/import-url (routing.md). Nothing about
it is special to one creator, host or series: any release this app can
read is added the same way.
The diagram's source
flowchart LR
accTitle: Importing a release by URL
accDescr: The import screen reads a release from any host, validates it against the shapes, and imports it into the learner's instance.
ui["ImportUrlContainer / ImportUrlScreen<br/>#/import-url?instance=…&url=…"] --> read["readReleaseFromLink"]
read --> lib["DeckLibrary.readRelease<br/>(solidDeckLibrary.ts)"]
read --> shapes["ShapeValidator.validateDocument<br/>(context library)"]
ui --> add["importReleaseFromUrl"]
add --> imp["importLibraryDeck / startCourse"]
lib -->|plain fetch| rel["any host: …/name/vN.ttl"]
shapes -->|plain fetch| rel
imp -->|authenticated| pod["the learner's instance"]- The learner pastes the link. It must be an http(s) address of a
document, with no fragment (
releaseUrlOf). It goes into the URL as the URL standard writes it, a host in capitals or a default port aside, so Back and a bookmark work and the copy names its release one way. readReleaseFromLinkreads the document with no index (DeckLibrary.readRelease,toStandaloneLibraryDeckin libraryMapper.ts): its deck, its cards counted, its creators and license, and its series with every version the document describes (format 6 describes it in the release; adecks/release at an older format lists only itself). It is read afresh each time, never kept: an address may hold anything, now or later.- The same read is checked against the library's shapes, as anyone reads
it, with no login sent (
validateDocument(url, "library")). A subject that breaks its shape refuses the release (releaseNotConforming); warnings do not. A release in a newer format than the app reads is refused as the library's are. - The screen shows the release as the library's page does, with where
it is published (its host) and whether it is a deck or a course, and
says it is not the library's. Its text is shown as the library's is,
any link through
ExternalLink; its cards and a course's theory, once added, are shown with Markdown's safety limits, as any data is. There is no card list or preview. importReleaseFromUrlthen imports a deck (importLibraryDeck) or starts a course (startCourse). The copy names its release withprov:wasDerivedFrom <url>, as a library copy does. Nothing is ever written to the release's host: every write goes to the learner's instance, checked as every write is.- A copy whose series the library's index does not list is a copy from
a link (
useCopiesin deckTreeEditor.ts). The deck list says "from <host>" after its name, and the Studio's Home a "From <host>" badge. Its own release says whether it is a course, read once. - Newer versions. No index lists the versions of a release from a
link. The creator's catalog may: it links what its instance
published (
sm:publishedRelease, data-model.md).DeckLibrary.publishedBesidelooks for it in the folders above the release, nearest first, as anyone reads them, and takes the first catalog that links the release. Each release it links is read, but those the copy's release lists, which are no newer, and the newest of the same series is the copy's upgrade (listLibraryUpdates,planLibraryUpgrade, the Studio's library copies).listLibraryUpdatesreads each release once for all the copies. An instance's catalog is private unless its owner shares it; then whether a newer version is out is unknown, and the Studio says so. - An upgrade from a link reads releases no index lists. Before it is
planned, the newer release and each one in between are checked against
the library's shapes, as the first was (
checkLinkedRelease); one that breaks them refuses the upgrade (releaseNotConforming).