// kapi-works-package.d.ts: the data files of a Kapi Works package (kapi.works.package/2, SDK 2.1). // Every text a learner reads is a text ID, a key in words/.json. Each file may name its JSON Schema in // "$schema": https://docs.kapiworks.com/schemas/. /** Lower-case letters, digits and hyphens, starting with a letter or digit: up to 64 characters. */ export type WorksId = string; /** A page ID: as a WorksId, up to 48 characters. Lesson and page IDs are all different. */ export type WorksPageId = string; /** A key in the words file, such as "first-week.title": parts of lower-case letters, digits, "_" and "-" joined by * dots, up to 128 characters. The first part is its lesson, page or question ID, or course, common, role or assessment. */ export type WorksTextId = string; /** A BCP 47 language tag, such as "en-GB". */ export type WorksLanguage = string; /** A path under media/, such as "media/laptop-setup.png". */ export type WorksMediaPath = string; /** What a course is for. It sets the type checks Kapi runs. */ export type WorksCourseType = "training-course" | "scenario-course" | "quick-refresher" | "certificate-course" | "walkthrough" | "what-changed" | "video"; export type WorksBlockType = "heading" | "text" | "list" | "steps" | "callout" | "table" | "quote" | "image" | "audio" | "video" | "film" | "question" | "contact" | "sources" | "divider"; export type WorksQuestionKind = "choice" | "multi" | "exact" | "order" | "match"; export type WorksMediaKind = "image" | "audio" | "video" | "font"; /** What Kapi fills in when the package opens. contacts. is a contact the company fills in, such as contacts.it-help. */ export type WorksContextKey = "company.name" | "door" | "role" | "learner.firstName" | `contacts.${string}`; /** kapi-works.json: what the package is, how it plays, and what it declares. */ export interface WorksPackageManifest { $schema?: string; schema: "kapi.works.package/2"; /** The SDK the package needs. Kapi serves up to "2.1". */ sdk: string; /** The same in every version of the package. */ id: WorksId; kind: "course"; /** Only a course has one. Left out: "training-course". */ courseType?: WorksCourseType; title: WorksTextId; summary?: WorksTextId; /** The author's name for this version: one line, up to 40 characters. */ label?: string; /** One language for now: { default: "en-GB", files: { "en-GB": "words/en-GB.json" } }. */ languages: { default: WorksLanguage; files: Record }; /** Kapi's player for the package's kind, and the line it plays on. */ view: { player: "course"; line: "1.0" }; /** The version of what the package saves for each person, such as "new-hire-welcome/v2". */ state?: string; /** Up to 32 roles that lessons can be required for. Mark one as the default. */ roles?: WorksRole[]; /** Up to 8 tests, each drawn from a pool of held questions. */ assessments?: WorksAssessment[]; context?: Partial>; /** The hosts the words may link to, such as "www.hse.gov.uk": up to 50. */ links?: string[]; media?: WorksMediaEntry[]; /** The documents the package was made from. */ sources?: WorksSource[]; } export interface WorksRole { id: WorksId; title: WorksTextId; /** On the role for people whose job role isn't mapped to one of these. */ default?: true; } export interface WorksAssessment { id: WorksId; title: WorksTextId; /** The IDs of the questions the test draws from. These held questions sit on no page. */ pool: WorksId[]; /** The settings Kapi suggests; the company decides. A draw of up to 50, a pass mark from 1 to 100, 1 to 10 attempts. */ suggest?: { draw: number; passMark: number; attempts?: number }; } export interface WorksMediaEntry { id: WorksId; file: WorksMediaPath; /** What the file is. Kapi reads its first bytes, not its name. */ kind: WorksMediaKind; /** "sha256:" and 64 hex digits: a picture the company added in Works Studio. Kapi keeps it, so its file can stay out * of the package. A file in the package must have this SHA-256. */ sha256?: string; /** What the picture shows, in a sentence. A picture needs it unless it's decorative. */ alt?: WorksTextId | null; decorative?: true; /** The sound or video has speech, which needs captions. */ speech?: boolean; /** A WebVTT file under media/ for each language. */ captions?: Record; } export interface WorksSource { /** Up to 200 characters. */ title: string; /** The SHA-256 of the document's text: "sha256:" and 64 hex digits. */ sha256?: string; } /** content.json: a course's lessons, pages and blocks, in reading order. */ export interface WorksContent { $schema?: string; schema: "kapi.works.content/2"; /** Up to 64 lessons and 200 pages in all. */ lessons: WorksLesson[]; } export interface WorksLesson { id: WorksId; title: WorksTextId; /** About how many minutes it takes, from 1 to 120. */ minutes?: number; /** The role IDs it's required for. null or left out: everyone. */ requiredFor?: WorksId[] | null; pages: WorksPage[]; } export interface WorksPage { id: WorksPageId; title: WorksTextId; blocks?: WorksBlock[]; } /** A block on a page. image, audio and video show a media ID, question a question ID that isn't in a test's pool, and * contact the key of a contacts. in context. A film needs "sdk": "2.1". */ export type WorksBlock = | { type: "heading"; text: WorksTextId } | { type: "text"; text: WorksTextId; style?: "body" | "lead" | "small" } | { type: "list"; items: WorksTextId[]; ordered?: boolean } | { type: "steps"; items: WorksTextId[] } | { type: "callout"; tone: "note" | "important" | "warning"; text: WorksTextId } | { type: "table"; header?: WorksTextId[]; rows: WorksTextId[][] } | { type: "quote"; text: WorksTextId; cite?: WorksTextId } | { type: "image"; media: WorksId } | { type: "audio"; media: WorksId } | { type: "video"; media: WorksId } | { type: "film"; scenes: WorksFilmScene[]; emails?: WorksFilmEmail[]; style?: string } | { type: "question"; question: WorksId } | { type: "contact"; contact: WorksId } | { type: "sources" } | { type: "divider" }; /** An email a film draws once. Its email scenes point at it by ID. Up to 4 in a film. */ export interface WorksFilmEmail { id: WorksId; from: WorksTextId; /** Use an address that ends in .example. */ address: WorksTextId; subject: WorksTextId; /** 1 to 4 lines. */ lines: WorksTextId[]; button?: WorksTextId; /** The address the button really goes to, shown when the pointer rests on it. */ link?: WorksTextId; attachment?: WorksTextId; } /** A film's scene: 2 to 30 in a film, up to 10 minutes in all. */ export type WorksFilmScene = { id: WorksId; /** Its name in the transcript and on the seek bar. */ title: WorksTextId; /** 4 to 60, in tenths at most, such as 6.5. */ seconds: number; /** What the narrator says, shown as captions. Short sentences, and never a placeholder. */ caption: WorksTextId; /** What the scene shows, for the transcript and screen readers. */ picture: WorksTextId; /** A few words on the picture. */ headline?: WorksTextId; /** A choice or multi practice question the film stops for after this scene. No page or test pool holds it too. */ pause?: WorksId; } & ( | { layout: "title"; heading: WorksTextId; kicker?: WorksTextId } | { layout: "number"; number: WorksTextId; label: WorksTextId; note?: WorksTextId } | { layout: "email"; email: WorksId; beats?: ("arrive" | "mark-from" | "reveal-address" | "mark-subject" | "mark-lines" | "point-link" | "mark-attachment" | "report")[] } | { layout: "chat"; messages: { who: WorksTextId; text: WorksTextId }[]; mark?: number } | { layout: "person"; label: WorksTextId; prop: "phone" | "laptop" | "none"; with?: WorksTextId; says?: WorksTextId } | { layout: "icons"; items: { icon: "search" | "phone" | "flag" | "mail" | "link" | "file" | "lock" | "key" | "shield" | "alert" | "check" | "clock" | "card" | "person"; text: WorksTextId }[] } | { layout: "checklist"; items: WorksTextId[] } /** The author's own drawing on a 960 by 540 canvas: markup whose words are text IDs, such as * , styled by the film's style. */ | { layout: "drawing"; drawing: string } ); /** The layouts a scene can use: seven Kapi draws, and the author's own drawing. */ export type WorksFilmLayout = "title" | "number" | "email" | "chat" | "person" | "icons" | "checklist" | "drawing"; /** questions.json: practice questions sit on pages; held questions sit only in a test's pool. */ export interface WorksQuestions { $schema?: string; schema: "kapi.works.questions/2"; /** Up to 2,000. */ questions: WorksQuestion[]; } export type WorksQuestion = { id: WorksId; prompt: WorksTextId; /** The situation the question is set in, shown before it. */ situation?: WorksTextId; /** Why the answer is right, shown after an answer. */ explanation?: WorksTextId; } & ( | { kind: "choice"; choices: WorksChoice[]; answer: WorksId } | { kind: "multi"; choices: WorksChoice[]; answer: WorksId[] } | { kind: "exact"; accepted: string[]; match: "prose" | "exact" } | { kind: "order"; items: WorksItem[]; answer: WorksId[] } | { kind: "match"; items: WorksItem[]; groups: WorksItem[]; answer: Record } ); /** 2 to 10 for each question. */ export interface WorksChoice { id: WorksId; text: WorksTextId; /** Why this choice is right or wrong, shown after an answer. */ feedback?: WorksTextId; } /** An item to order or sort, or a group: at least 2 of each. */ export interface WorksItem { id: WorksId; text: WorksTextId; } /** words/.json: every text in the package, by text ID, up to 1 MiB. Words take only **strong**, * _emphasis_, [label](https://host/path) links and line breaks, and the placeholders {company.name}, * {contacts..name}, .email, .phone, .url and {learner.firstName}. */ export type WorksWords = Record; /** support.json: where each answer key and fact comes from, quoted word for word from its source. */ export interface WorksSupport { $schema?: string; schema: "kapi.works.support/1"; items: WorksSupportItem[]; } export interface WorksSupportItem { /** A question ID or a text ID. */ for: string; /** The source's SHA-256, as in the manifest's sources. */ source: string; /** The ID of the part of the source the quote is from. */ unit?: string; /** The passage, word for word: up to 2,000 characters. */ quote: string; } /** look/theme.json: the package's look, which starts from the company's brand. */ export interface WorksTheme { $schema?: string; schema: "kapi.works.theme/1"; base: "company"; tokens?: { /** 0 to 24. */ radius?: number; /** 0.75 to 1.25. */ density?: number; /** 0.875 to 1.25. */ typeScale?: number }; cover?: { media: WorksId; style: "split" | "full" | "band" }; } /** A bundle: the package's data as one JSON block, for an AI that writes text rather than files. */ export interface WorksBundle { $schema?: string; schema: "kapi.works.bundle/1"; /** This part's number, when the bundle comes in parts. */ part?: number; /** How many parts there are, up to 20. */ of?: number; /** Each file by its path: JSON files as JSON or as text, other files as text. Media come separately, by ID. */ files: { "kapi-works.json"?: WorksPackageManifest | string; "content.json"?: WorksContent | string; "questions.json"?: WorksQuestions | string; "support.json"?: WorksSupport | string; "look/theme.json"?: WorksTheme | string; [path: `words/${string}.json`]: WorksWords | string; [path: string]: unknown; }; }