Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions AgentGuidelines/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions AgentGuidelines/Guidelines/Packages.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions AgentGuidelines/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions AgentGuidelines/Scripts/validate_guidelines.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions AgentGuidelines/Tests/run_tests.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion AgentGuidelines/VERSION

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

# FlashcardKit

FlashcardKit is a reusable, UI-agnostic domain package for prompt-and-answer recall activities. Its persistence-friendly card values support text and opaque host-owned asset references without taking ownership of presentation or media resolution.
FlashcardKit is a reusable, UI-agnostic domain package for prompt-and-answer recall activities. Its persistence-friendly card values support one or more ordered recall stages containing text and opaque host-owned asset references without taking ownership of presentation or media resolution.

```swift
let card = Flashcard(
Expand All @@ -20,6 +20,22 @@ let card = Flashcard(
answer: try FlashcardContent(text: "dog")
)

let stagedCard = try Flashcard(
id: UUID(),
stages: [
FlashcardStage(
id: FlashcardStageID(rawValue: "definition"),
prompt: try FlashcardContent(text: "das Haus"),
answer: try FlashcardContent(text: "house")
),
FlashcardStage(
id: FlashcardStageID(rawValue: "article"),
prompt: try FlashcardContent(text: "Haus"),
answer: try FlashcardContent(text: "das")
),
]
)

var session = try ThreeChoiceSession(
cards: cards,
configuration: ThreeChoiceSessionConfiguration(seed: 42, roundCount: 5)
Expand All @@ -33,6 +49,8 @@ if let round = session.currentRound {
}
```

The simple initializer remains the shortest path for one-stage cards. Stage-aware hosts can provide a nonempty ordered sequence with stable, unique stage identifiers. The current `ThreeChoiceSession` continues to use each card's first stage; progressive stage scheduling is a separate API step.

The session plan is reproducible for identical cards, configuration, and seed. Each round exposes one correct answer and two distinct distractors; a host can submit a selected choice or explicit expiry. Presentation, timers, persistence frameworks, image resolution, vocabulary acquisition, and spaced repetition remain outside the package boundary.

## Installation
Expand Down
77 changes: 71 additions & 6 deletions Sources/FlashcardKit/Flashcard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@ public struct Flashcard: Identifiable, Codable, Sendable, Hashable {
/// The stable identity supplied by the host.
public let id: UUID

/// Content presented for recall.
public let prompt: FlashcardContent
/// Ordered recall stages authored by the host.
public let stages: [FlashcardStage]

/// Content revealed or evaluated as the answer.
public let answer: FlashcardContent
/// Content presented by the first stage.
///
/// This compatibility property preserves the simple-card API. Stage-aware consumers should use ``stages``.
public var prompt: FlashcardContent {
stages[0].prompt
}

/// Content evaluated as the first stage's answer.
///
/// This compatibility property preserves the simple-card API. Stage-aware consumers should use ``stages``.
public var answer: FlashcardContent {
stages[0].answer
}

/// Creates a flashcard from validated prompt and answer content.
///
Expand All @@ -19,7 +30,61 @@ public struct Flashcard: Identifiable, Codable, Sendable, Hashable {
/// - answer: Content revealed or evaluated as the answer.
public init(id: UUID, prompt: FlashcardContent, answer: FlashcardContent) {
self.id = id
self.prompt = prompt
self.answer = answer
stages = [FlashcardStage(id: .primary, prompt: prompt, answer: answer)]
}

/// Creates a flashcard from one or more ordered recall stages.
///
/// - Parameters:
/// - id: Stable host-owned identity.
/// - stages: Nonempty ordered stages with unique host-authored identities.
/// - Throws: ``FlashcardError`` when no stages are supplied or stage identities are duplicated.
public init(id: UUID, stages: [FlashcardStage]) throws {
guard !stages.isEmpty else {
throw FlashcardError.missingStages
}

var stageIDs = Set<FlashcardStageID>()
for stage in stages where !stageIDs.insert(stage.id).inserted {
throw FlashcardError.duplicateStageID(stage.id)
}

self.id = id
self.stages = stages
}

/// Decodes current staged cards and cards encoded by the original simple-card model.
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(UUID.self, forKey: .id)
if let stages = try container.decodeIfPresent([FlashcardStage].self, forKey: .stages) {
try self.init(id: id, stages: stages)
} else {
self.init(
id: id,
prompt: try container.decode(FlashcardContent.self, forKey: .prompt),
answer: try container.decode(FlashcardContent.self, forKey: .answer)
)
}
}

/// Encodes staged cards while retaining the original representation for simple-card readers.
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(stages, forKey: .stages)
if stages.count == 1 {
try container.encode(prompt, forKey: .prompt)
try container.encode(answer, forKey: .answer)
}
}

// MARK: - Private

private enum CodingKeys: String, CodingKey {
case answer
case id
case prompt
case stages
}
}
10 changes: 10 additions & 0 deletions Sources/FlashcardKit/FlashcardError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

/// Validation failures produced while creating a flashcard.
public enum FlashcardError: Error, Sendable, Equatable {
/// More than one stage used the same host-authored identity.
case duplicateStageID(FlashcardStageID)

/// No recall stage was supplied.
case missingStages
}
24 changes: 23 additions & 1 deletion Sources/FlashcardKit/FlashcardKit.docc/FlashcardKit.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Build deterministic, UI-agnostic recall activities from host-owned prompt-and-an

## Overview

Use ``Flashcard`` to give recall activities stable host-owned identity, prompt content, and answer content. ``FlashcardContent`` can carry text, an opaque host-owned asset reference, or both, while preserving a nonempty representation invariant across creation and decoding.
Use ``Flashcard`` to give recall activities stable host-owned identity and one or more ordered ``FlashcardStage`` values. Each stage has a host-authored ``FlashcardStageID``, prompt content, and answer content. ``FlashcardContent`` can carry text, an opaque host-owned asset reference, or both, while preserving a nonempty representation invariant across creation and decoding.

```swift
let card = Flashcard(
Expand All @@ -14,6 +14,28 @@ let card = Flashcard(
)
```

The simple initializer constructs one stage with ``FlashcardStageID/primary``. Stage-aware hosts can create a card from a nonempty sequence whose identifiers are unique within that card:

```swift
let stagedCard = try Flashcard(
id: UUID(),
stages: [
FlashcardStage(
id: FlashcardStageID(rawValue: "definition"),
prompt: try FlashcardContent(text: "das Haus"),
answer: try FlashcardContent(text: "house")
),
FlashcardStage(
id: FlashcardStageID(rawValue: "article"),
prompt: try FlashcardContent(text: "Haus"),
answer: try FlashcardContent(text: "das")
),
]
)
```

Stage order is significant and preserved by `Codable`. ``FlashcardError`` reports missing stages and duplicate stage identifiers. Current ``ThreeChoiceSession`` behavior remains one-stage-compatible by reading each card's first stage; stage progression is intentionally separate from the card value model.

The package does not resolve asset references or own presentation, persistence frameworks, vocabulary acquisition, or scheduling policy.

Use ``ThreeChoiceSession`` to create a finite seeded plan with one correct answer and two distinct distractors per round. The host owns any timer and submits either ``ThreeChoiceResponse/selection(choiceID:)`` or ``ThreeChoiceResponse/expired`` against the exact visible round identity.
Expand Down
Loading