@khorsheed/dsh-library 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.en.md +49 -0
  3. package/README.i18n.yaml +6 -0
  4. package/README.md +48 -2
  5. package/cordis.patch.yml +17 -0
  6. package/lib/client.js +66276 -0
  7. package/lib/index.js +3318 -0
  8. package/lib/profiles-BXM8xNUf.js +1608 -0
  9. package/lib/profiles-BrbeM-bi.js +1635 -0
  10. package/lib/profiles-CFrPQdfO.js +1692 -0
  11. package/lib/profiles-DxtzIUvd.js +1632 -0
  12. package/lib/profiles-O0ddEit3.js +1631 -0
  13. package/lib/profiles-Oj-zBAY9.js +1692 -0
  14. package/lib/profiles-gIEviEuO.js +1610 -0
  15. package/lib/store-CIcZgTsg.js +1456 -0
  16. package/lib/store-DfYOmS44.js +1418 -0
  17. package/lib/tool.js +1597 -0
  18. package/lib/typert.host.d.ts +3 -0
  19. package/lib/typert.host.js +3811 -0
  20. package/lib/typert.remote-client.d.ts +137 -0
  21. package/lib/typert.remote-client.js +3077 -0
  22. package/lib/types/cards.d.ts +41 -0
  23. package/lib/types/cards.js +111 -0
  24. package/lib/types/client/LibraryPane.d.ts +26 -0
  25. package/lib/types/client/LibraryPane.js +117 -0
  26. package/lib/types/client/bind/BindChip.d.ts +33 -0
  27. package/lib/types/client/bind/BindChip.js +102 -0
  28. package/lib/types/client/contract.d.ts +164 -0
  29. package/lib/types/client/contract.js +1 -0
  30. package/lib/types/client/covers.d.ts +36 -0
  31. package/lib/types/client/covers.js +66 -0
  32. package/lib/types/client/definition.d.ts +27 -0
  33. package/lib/types/client/definition.js +32 -0
  34. package/lib/types/client/icons-local.d.ts +41 -0
  35. package/lib/types/client/icons-local.js +25 -0
  36. package/lib/types/client/icons.d.ts +27 -0
  37. package/lib/types/client/icons.js +11 -0
  38. package/lib/types/client/index.d.ts +35 -0
  39. package/lib/types/client/index.js +219 -0
  40. package/lib/types/client/judge.d.ts +70 -0
  41. package/lib/types/client/judge.js +262 -0
  42. package/lib/types/client/locales.d.ts +553 -0
  43. package/lib/types/client/locales.js +1076 -0
  44. package/lib/types/client/look.d.ts +63 -0
  45. package/lib/types/client/look.js +64 -0
  46. package/lib/types/client/math.d.ts +24 -0
  47. package/lib/types/client/math.js +47 -0
  48. package/lib/types/client/mistakes.d.ts +21 -0
  49. package/lib/types/client/mistakes.js +28 -0
  50. package/lib/types/client/preset-visibility.d.ts +109 -0
  51. package/lib/types/client/preset-visibility.js +123 -0
  52. package/lib/types/client/quiz-drafts.d.ts +29 -0
  53. package/lib/types/client/quiz-drafts.js +50 -0
  54. package/lib/types/client/store.d.ts +109 -0
  55. package/lib/types/client/store.js +44 -0
  56. package/lib/types/client/titles.d.ts +17 -0
  57. package/lib/types/client/titles.js +23 -0
  58. package/lib/types/client/ui.d.ts +96 -0
  59. package/lib/types/client/ui.js +124 -0
  60. package/lib/types/client/views/book.d.ts +57 -0
  61. package/lib/types/client/views/book.js +164 -0
  62. package/lib/types/client/views/cards.d.ts +17 -0
  63. package/lib/types/client/views/cards.js +53 -0
  64. package/lib/types/client/views/home.d.ts +26 -0
  65. package/lib/types/client/views/home.js +263 -0
  66. package/lib/types/client/views/lesson.d.ts +42 -0
  67. package/lib/types/client/views/lesson.js +146 -0
  68. package/lib/types/client/views/path.d.ts +32 -0
  69. package/lib/types/client/views/path.js +64 -0
  70. package/lib/types/client/views/quiz.d.ts +42 -0
  71. package/lib/types/client/views/quiz.js +366 -0
  72. package/lib/types/client/views/station-quiz.d.ts +43 -0
  73. package/lib/types/client/views/station-quiz.js +203 -0
  74. package/lib/types/client/views/station.d.ts +18 -0
  75. package/lib/types/client/views/station.js +142 -0
  76. package/lib/types/image-refs.d.ts +35 -0
  77. package/lib/types/image-refs.js +71 -0
  78. package/lib/types/index.d.ts +51 -0
  79. package/lib/types/index.js +31 -0
  80. package/lib/types/mastery.d.ts +81 -0
  81. package/lib/types/mastery.js +110 -0
  82. package/lib/types/profiles.d.ts +31 -0
  83. package/lib/types/profiles.js +25 -0
  84. package/lib/types/remote.d.ts +355 -0
  85. package/lib/types/remote.js +332 -0
  86. package/lib/types/service/assess.d.ts +144 -0
  87. package/lib/types/service/assess.js +773 -0
  88. package/lib/types/service/bindings.d.ts +92 -0
  89. package/lib/types/service/bindings.js +198 -0
  90. package/lib/types/service/cards.d.ts +85 -0
  91. package/lib/types/service/cards.js +320 -0
  92. package/lib/types/service/core.d.ts +124 -0
  93. package/lib/types/service/core.js +169 -0
  94. package/lib/types/service/pages.d.ts +60 -0
  95. package/lib/types/service/pages.js +125 -0
  96. package/lib/types/service/roadmap.d.ts +99 -0
  97. package/lib/types/service/roadmap.js +284 -0
  98. package/lib/types/service/rules.d.ts +63 -0
  99. package/lib/types/service/rules.js +245 -0
  100. package/lib/types/service/shelf.d.ts +136 -0
  101. package/lib/types/service/shelf.js +422 -0
  102. package/lib/types/service.d.ts +42 -0
  103. package/lib/types/service.js +41 -0
  104. package/lib/types/store.d.ts +257 -0
  105. package/lib/types/store.js +1376 -0
  106. package/lib/types/tool-definition.d.ts +49 -0
  107. package/lib/types/tool-definition.js +49 -0
  108. package/lib/types/tool.d.ts +71 -0
  109. package/lib/types/tool.js +174 -0
  110. package/lib/types/tools/assess.d.ts +11 -0
  111. package/lib/types/tools/assess.js +82 -0
  112. package/lib/types/tools/binding.d.ts +11 -0
  113. package/lib/types/tools/binding.js +46 -0
  114. package/lib/types/tools/cards.d.ts +11 -0
  115. package/lib/types/tools/cards.js +100 -0
  116. package/lib/types/tools/kit.d.ts +215 -0
  117. package/lib/types/tools/kit.js +281 -0
  118. package/lib/types/tools/notes.d.ts +11 -0
  119. package/lib/types/tools/notes.js +40 -0
  120. package/lib/types/tools/page.d.ts +11 -0
  121. package/lib/types/tools/page.js +59 -0
  122. package/lib/types/tools/project.d.ts +11 -0
  123. package/lib/types/tools/project.js +67 -0
  124. package/lib/types/tools/quiz.d.ts +11 -0
  125. package/lib/types/tools/quiz.js +74 -0
  126. package/lib/types/tools/read.d.ts +11 -0
  127. package/lib/types/tools/read.js +127 -0
  128. package/lib/types/tools/resource.d.ts +11 -0
  129. package/lib/types/tools/resource.js +147 -0
  130. package/lib/types/tools/roadmap.d.ts +11 -0
  131. package/lib/types/tools/roadmap.js +102 -0
  132. package/lib/types/types.d.ts +752 -0
  133. package/lib/types/types.js +159 -0
  134. package/locale/en.json +6 -0
  135. package/locale/zh.json +6 -0
  136. package/package.json +135 -3
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Session bindings, the per-turn binding summary, events, activity and stats — the top layer, exported as LibraryService.
3
+ *
4
+ * One layer of the library service (see `service/core.ts` for the layering):
5
+ * the methods are the service's own, moved here verbatim by domain.
6
+ *
7
+ * @module @khorsheed/dsh-library/service/bindings
8
+ */
9
+ import { type LibraryBinding, type LibraryCard, type LibraryEvent, type LibraryEventType, type LibraryOutcome, type LibraryProject, type LibraryResource, type LibraryResourceProgress } from '../types.ts';
10
+ import type { LibraryStationMastery } from '../mastery.ts';
11
+ import { LibraryServiceAssess } from './assess.ts';
12
+ export declare class LibraryService extends LibraryServiceAssess {
13
+ /** The project / resource a session works on, when it is bound. */
14
+ getBinding(sessionId: string): LibraryBinding | undefined;
15
+ /** Bind a session (null unbinds). The project and resource must exist. */
16
+ setBinding(sessionId: string, binding: LibraryBinding | null): Promise<LibraryOutcome<{
17
+ binding: LibraryBinding | null;
18
+ }>>;
19
+ /**
20
+ * The bound session's working set, read synchronously (the per-turn prompt
21
+ * context renders inside a synchronous callback): the project, the bound
22
+ * resource, every textbook's path state in study order (so the coach reads
23
+ * what the learner adopted, discarded or reordered in the sidebar, every
24
+ * turn), and the weakest live cards.
25
+ */
26
+ bindingSummary(sessionId: string, cardLimit?: number): {
27
+ binding?: LibraryBinding;
28
+ project?: LibraryProject;
29
+ resource?: LibraryResource;
30
+ textbooks: LibraryResourceProgress[];
31
+ /** The bound textbook's current station and its points' mastery (where the coach is teaching now). */
32
+ station?: {
33
+ id: string;
34
+ title: string;
35
+ mastery: LibraryStationMastery;
36
+ };
37
+ weakCards: LibraryCard[];
38
+ };
39
+ /** One project's audit trail, newest first, optionally some types only. */
40
+ listEvents(request: {
41
+ projectId: string;
42
+ limit?: number;
43
+ types?: readonly LibraryEventType[];
44
+ }): Promise<{
45
+ events: LibraryEvent[];
46
+ }>;
47
+ /** 「教练最近记下」: card recordings from chats and quizzes, across projects, newest first. */
48
+ /** A project's name, when it exists (synchronous — the per-turn context reads it). */
49
+ projectName(projectId: string): string | undefined;
50
+ /** The newest knowledge-point state changes across projects (今日 「最近的变化」). */
51
+ recentChanges(request: {
52
+ limit?: number;
53
+ }): Promise<{
54
+ changes: (LibraryEvent & {
55
+ projectId: string;
56
+ projectName: string;
57
+ })[];
58
+ }>;
59
+ recentRecords(request: {
60
+ limit?: number;
61
+ }): Promise<{
62
+ records: (LibraryEvent & {
63
+ projectId: string;
64
+ projectName: string;
65
+ })[];
66
+ }>;
67
+ /**
68
+ * The newest event's timestamp across the library — the pane's change poll.
69
+ * The Remote face is request/response (no push), so the sidebar learns
70
+ * host-side writes (a landed quiz, a recorded page) by polling this and
71
+ * re-reading when it moves. Writes that log no event (creating a project)
72
+ * move it too. `session` names the newest session-made write, so the client
73
+ * can reveal the library next to the conversation that just started using it.
74
+ */
75
+ latestActivity(): Promise<{
76
+ at?: string;
77
+ session?: {
78
+ id: string;
79
+ at: string;
80
+ };
81
+ }>;
82
+ /** The whole-library tally. */
83
+ stats(): Promise<{
84
+ projects: number;
85
+ books: number;
86
+ cards: number;
87
+ due: number;
88
+ quizzes: number;
89
+ attempts: number;
90
+ }>;
91
+ }
92
+ //# sourceMappingURL=bindings.d.ts.map
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Session bindings, the per-turn binding summary, events, activity and stats — the top layer, exported as LibraryService.
3
+ *
4
+ * One layer of the library service (see `service/core.ts` for the layering):
5
+ * the methods are the service's own, moved here verbatim by domain.
6
+ *
7
+ * @module @khorsheed/dsh-library/service/bindings
8
+ */
9
+ import { isThickResource, } from "../types.js";
10
+ import { refuse, storeRefusal } from "./core.js";
11
+ import { LibraryServiceAssess } from "./assess.js";
12
+ export class LibraryService extends LibraryServiceAssess {
13
+ /* ------------------------------------------------------------- bindings */
14
+ /** The project / resource a session works on, when it is bound. */
15
+ getBinding(sessionId) {
16
+ this.assertAlive('getBinding');
17
+ return this.store.readBindings().get(sessionId);
18
+ }
19
+ /** Bind a session (null unbinds). The project and resource must exist. */
20
+ async setBinding(sessionId, binding) {
21
+ this.assertAlive('setBinding');
22
+ if (sessionId === '')
23
+ return refuse('invalid', 'no session to bind');
24
+ if (binding !== null) {
25
+ const project = this.readProjectOrRefusal(binding.projectId);
26
+ if ('error' in project)
27
+ return project;
28
+ if (binding.resourceId !== undefined && this.store.readResource(binding.projectId, binding.resourceId) === undefined) {
29
+ return refuse('not-found', `no resource ${JSON.stringify(binding.resourceId)} in project ${JSON.stringify(project.value.name)} — resource ids are minted (r_…), never chosen by the caller; library_read · resources lists them. Registering the resource (library_textbook) binds this session itself — no separate library_binding call needed`);
30
+ }
31
+ }
32
+ try {
33
+ await this.store.withBindingsLock(() => {
34
+ const bindings = new Map(this.store.readBindings());
35
+ // A switch to another project remembers where the session came from
36
+ // (the coach is told every turn that the earlier talk is not this
37
+ // project's material — a reused session once quizzed 数学 inside 神经科学).
38
+ const before = bindings.get(sessionId);
39
+ const previous = before === undefined ? undefined : before.projectId !== binding?.projectId ? before.projectId : before.previousProjectId;
40
+ if (binding === null)
41
+ bindings.delete(sessionId);
42
+ else
43
+ bindings.set(sessionId, { projectId: binding.projectId, ...(binding.resourceId === undefined ? {} : { resourceId: binding.resourceId }), ...(previous === undefined ? {} : { previousProjectId: previous }) });
44
+ this.store.writeBindings(bindings);
45
+ });
46
+ if (binding !== null)
47
+ this.changed(binding.projectId);
48
+ return { ok: true, binding };
49
+ }
50
+ catch (error) {
51
+ return storeRefusal(error);
52
+ }
53
+ }
54
+ /**
55
+ * The bound session's working set, read synchronously (the per-turn prompt
56
+ * context renders inside a synchronous callback): the project, the bound
57
+ * resource, every textbook's path state in study order (so the coach reads
58
+ * what the learner adopted, discarded or reordered in the sidebar, every
59
+ * turn), and the weakest live cards.
60
+ */
61
+ bindingSummary(sessionId, cardLimit = 12) {
62
+ this.assertAlive('bindingSummary');
63
+ const binding = this.getBinding(sessionId);
64
+ if (binding === undefined)
65
+ return { textbooks: [], weakCards: [] };
66
+ let project;
67
+ try {
68
+ project = this.store.readProject(binding.projectId);
69
+ }
70
+ catch {
71
+ project = undefined;
72
+ }
73
+ if (project === undefined)
74
+ return { binding, textbooks: [], weakCards: [] };
75
+ const resource = binding.resourceId === undefined ? undefined : this.store.readResource(project.id, binding.resourceId);
76
+ const textbooks = this.store.listResources(project.id).filter(isThickResource).map(textbook => this.resourceProgress(project.id, textbook));
77
+ const weakCards = this.store.readCards(project.id)
78
+ .filter(card => card.status === 'active' && card.weakness > 0 && (resource === undefined || card.resourceId === resource.id))
79
+ .sort((a, b) => b.weakness - a.weakness || b.updatedAt.localeCompare(a.updatedAt))
80
+ .slice(0, cardLimit);
81
+ const current = resource === undefined ? undefined : this.store.readRoadmap(project.id, resource.id)?.nodes.find(node => node.state === 'current' && node.gate === 'quiz');
82
+ const mastery = current === undefined ? undefined : this.stationMasteryOf(project.id, current.id, resource?.id);
83
+ return {
84
+ binding,
85
+ project,
86
+ ...(resource === undefined ? {} : { resource }),
87
+ textbooks,
88
+ ...(current === undefined || mastery === undefined ? {} : { station: { id: current.id, title: current.title, mastery } }),
89
+ weakCards,
90
+ };
91
+ }
92
+ /* --------------------------------------------------------------- events */
93
+ /** One project's audit trail, newest first, optionally some types only. */
94
+ async listEvents(request) {
95
+ this.assertAlive('listEvents');
96
+ const types = request.types === undefined ? undefined : new Set(request.types);
97
+ const events = this.store.readEvents(request.projectId).filter(event => types === undefined || types.has(event.type));
98
+ return { events: events.reverse().slice(0, request.limit ?? 20) };
99
+ }
100
+ /** 「教练最近记下」: card recordings from chats and quizzes, across projects, newest first. */
101
+ /** A project's name, when it exists (synchronous — the per-turn context reads it). */
102
+ projectName(projectId) {
103
+ try {
104
+ return this.store.readProject(projectId)?.name;
105
+ }
106
+ catch {
107
+ return undefined;
108
+ }
109
+ }
110
+ /** The newest knowledge-point state changes across projects (今日 「最近的变化」). */
111
+ async recentChanges(request) {
112
+ this.assertAlive('recentChanges');
113
+ const changes = [];
114
+ for (const project of this.store.readIndex().projects) {
115
+ for (const event of this.store.readEvents(project.id)) {
116
+ if (event.type !== 'point.changed' || event.nodeId === undefined || event.resourceId === undefined || event.id === undefined)
117
+ continue;
118
+ // A change about a textbook, station or point that is gone (removed, re-planned away) is not shown.
119
+ const node = this.store.readRoadmap(project.id, event.resourceId)?.nodes.find(candidate => candidate.id === event.nodeId);
120
+ if (node?.points?.some(point => point.id === event.id) !== true)
121
+ continue;
122
+ changes.push({ ...event, projectId: project.id, projectName: project.name });
123
+ }
124
+ }
125
+ changes.sort((a, b) => b.at.localeCompare(a.at));
126
+ return { changes: changes.slice(0, request.limit ?? 10) };
127
+ }
128
+ async recentRecords(request) {
129
+ this.assertAlive('recentRecords');
130
+ const records = [];
131
+ for (const project of this.store.readIndex().projects) {
132
+ // A recording whose cards were all deleted since is not shown; one with
133
+ // some left names only the live ones.
134
+ const liveTerms = new Map(this.store.readCards(project.id).filter(card => card.status !== 'deleted').map(card => [card.id, card.term]));
135
+ for (const event of this.store.readEvents(project.id)) {
136
+ if (event.type !== 'cards.recorded' || (event.source !== 'chat' && event.source !== 'quiz'))
137
+ continue;
138
+ const ids = (event.ids ?? []).filter(cardId => liveTerms.has(cardId));
139
+ if (event.ids !== undefined && ids.length === 0)
140
+ continue;
141
+ records.push({
142
+ ...event,
143
+ ...(event.ids === undefined ? {} : { ids, title: ids.map(cardId => liveTerms.get(cardId)).join('、').slice(0, 300) }),
144
+ projectId: project.id,
145
+ projectName: project.name,
146
+ });
147
+ }
148
+ }
149
+ records.sort((a, b) => b.at.localeCompare(a.at));
150
+ return { records: records.slice(0, request.limit ?? 10) };
151
+ }
152
+ /**
153
+ * The newest event's timestamp across the library — the pane's change poll.
154
+ * The Remote face is request/response (no push), so the sidebar learns
155
+ * host-side writes (a landed quiz, a recorded page) by polling this and
156
+ * re-reading when it moves. Writes that log no event (creating a project)
157
+ * move it too. `session` names the newest session-made write, so the client
158
+ * can reveal the library next to the conversation that just started using it.
159
+ */
160
+ async latestActivity() {
161
+ this.assertAlive('latestActivity');
162
+ let latest = this.lastChangeAt;
163
+ for (const project of this.store.readIndex().projects) {
164
+ const at = this.store.readEvents(project.id).at(-1)?.at;
165
+ if (at !== undefined && (latest === undefined || at > latest))
166
+ latest = at;
167
+ }
168
+ return {
169
+ ...(latest === undefined ? {} : { at: latest }),
170
+ ...(this.lastSessionWrite === undefined ? {} : { session: { id: this.lastSessionWrite.session, at: this.lastSessionWrite.at } }),
171
+ };
172
+ }
173
+ /* --------------------------------------------------------------- stats */
174
+ /** The whole-library tally. */
175
+ async stats() {
176
+ this.assertAlive('stats');
177
+ const now = new Date().toISOString();
178
+ let books = 0;
179
+ let cards = 0;
180
+ let due = 0;
181
+ let quizzes = 0;
182
+ let attempts = 0;
183
+ const index = this.store.readIndex();
184
+ for (const project of index.projects) {
185
+ books += this.store.listResources(project.id).filter(isThickResource).length;
186
+ quizzes += this.store.listAssessments(project.id).filter(assessment => assessment.type === 'quiz').length;
187
+ attempts += this.store.readAttempts(project.id).length;
188
+ for (const card of this.store.readCards(project.id)) {
189
+ if (card.status !== 'active')
190
+ continue;
191
+ cards += 1;
192
+ if (card.mastery.dueAt <= now)
193
+ due += 1;
194
+ }
195
+ }
196
+ return { projects: index.projects.length, books, cards, due, quizzes, attempts };
197
+ }
198
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Knowledge cards: the dedupe write path, listing, editing, undo, review scheduling.
3
+ *
4
+ * One layer of the library service (see `service/core.ts` for the layering):
5
+ * the methods are the service's own, moved here verbatim by domain.
6
+ *
7
+ * @module @khorsheed/dsh-library/service/cards
8
+ */
9
+ import { type LibraryCardQuery, type LibraryCard, type LibraryCardInput, type LibraryCardWriteResult, type LibraryOutcome, type LibraryReviewRating } from '../types.ts';
10
+ import { LibraryServiceShelf } from './shelf.ts';
11
+ export declare class LibraryServiceCards extends LibraryServiceShelf {
12
+ /**
13
+ * Apply card inputs to the project's cards (call inside the project lock).
14
+ * Exact keys merge; near matches are held back unless `mergeNear` (photo
15
+ * words — a plural or a case away is the same word) or the input decides.
16
+ */
17
+ protected applyCardInputs(projectId: string, inputs: readonly LibraryCardInput[], defaultResourceId: string | undefined, options?: {
18
+ mergeNear?: boolean;
19
+ }): LibraryCardWriteResult & {
20
+ at: string;
21
+ };
22
+ /**
23
+ * Record cards (the coach's 「记下」, a manual add). Near-duplicates come back
24
+ * in `pending` with their candidates — resend each with `mergeInto` or `asNew`.
25
+ */
26
+ writeCards(request: {
27
+ projectId: string;
28
+ resourceId?: string;
29
+ cards: readonly LibraryCardInput[];
30
+ }): Promise<LibraryOutcome<LibraryCardWriteResult>>;
31
+ /** Cards, filtered and sorted (the sidebar list, the coach's search). */
32
+ listCards(request: LibraryCardQuery): Promise<{
33
+ cards: LibraryCard[];
34
+ total: number;
35
+ }>;
36
+ /** The cards due for review across projects (or one), weakest first. */
37
+ dueCards(request: {
38
+ projectId?: string;
39
+ limit?: number;
40
+ }): Promise<{
41
+ cards: (LibraryCard & {
42
+ projectId: string;
43
+ })[];
44
+ }>;
45
+ /** Edit a card by hand: its words, or its status (mastered / deleted / active). */
46
+ editCard(request: {
47
+ projectId: string;
48
+ cardId: string;
49
+ patch: {
50
+ term?: string;
51
+ gloss?: string;
52
+ userGloss?: string;
53
+ aliases?: readonly string[];
54
+ status?: LibraryCard['status'];
55
+ resourceId?: string | null;
56
+ };
57
+ }): Promise<LibraryOutcome<{
58
+ card: LibraryCard;
59
+ }>>;
60
+ /**
61
+ * Undo one recording (the toast's 撤销): cards it created are deleted; cards
62
+ * it merged into lose the source it stacked (and the weakness it added).
63
+ */
64
+ undoCards(request: {
65
+ projectId: string;
66
+ ids: readonly string[];
67
+ at: string;
68
+ }): Promise<LibraryOutcome<{
69
+ removed: number;
70
+ reverted: number;
71
+ }>>;
72
+ /**
73
+ * One flashcard review (the daily 复习): only the schedule moves — forgot
74
+ * resets to box 0 (tomorrow), fuzzy keeps the box, recalled climbs one.
75
+ * Reviews never unlock nodes and never touch weakness.
76
+ */
77
+ reviewCard(request: {
78
+ projectId: string;
79
+ cardId: string;
80
+ rating: LibraryReviewRating;
81
+ }): Promise<LibraryOutcome<{
82
+ card: LibraryCard;
83
+ }>>;
84
+ }
85
+ //# sourceMappingURL=cards.d.ts.map