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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ All notable changes to LexiconKit are documented here.

### Added

- Bootstrapped the Swift package, DocC catalog, CI/CD workflows, shared AgentGuidelines integration, and repository policy.
- Added persistence-friendly vocabulary entries, terms, definitions, provenance, tags, opaque asset references, and explicit timestamps.
- Added Codable round-trip and identity tests for the complete vocabulary model.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,20 @@

LexiconKit is a reusable, UI-agnostic domain package for personal vocabulary collections, definitions, provenance, lightweight metadata, and opaque references to host-owned media.

The package is currently a bootstrapped foundation. Dictionary lookup, translation, exercises, persistence frameworks, synchronization, and UI remain outside its boundary.
LexiconKit provides persistence-friendly values for vocabulary entries, terms, definitions, definition provenance, tags, and opaque host-owned media references. Dictionary lookup, translation, exercises, persistence frameworks, synchronization, and UI remain outside its boundary.

```swift
let entry = LexiconEntry(
term: LexiconTerm(text: "Haus", languageCode: "de"),
definitions: [
LexiconDefinition(
text: "house",
languageCode: "en",
source: .user
)
]
)
```

## Documentation

Expand Down
13 changes: 13 additions & 0 deletions Sources/LexiconKit/LexiconAssetReference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

/// An opaque stable reference to media owned and resolved by the host application.
public struct LexiconAssetReference: RawRepresentable, Codable, Hashable, Sendable {
public let rawValue: String

/// Creates an opaque host-owned asset reference.
///
/// - Parameter rawValue: The stable value understood by the host application.
public init(rawValue: String) {
self.rawValue = rawValue
}
}
28 changes: 28 additions & 0 deletions Sources/LexiconKit/LexiconDefinition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

/// A textual explanation associated with a vocabulary entry.
public struct LexiconDefinition: Identifiable, Codable, Hashable, Sendable {
public let id: UUID
public var languageCode: String?
public var source: LexiconDefinitionSource
public var text: String

/// Creates a definition with stable identity and explicit provenance.
///
/// - Parameters:
/// - id: The stable definition identifier.
/// - text: The definition text.
/// - languageCode: An optional BCP 47 language code for the text.
/// - source: The origin of the definition.
public init(
id: UUID = UUID(),
text: String,
languageCode: String? = nil,
source: LexiconDefinitionSource
) {
self.id = id
self.languageCode = languageCode
self.source = source
self.text = text
}
}
10 changes: 10 additions & 0 deletions Sources/LexiconKit/LexiconDefinitionSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

/// The provenance of a vocabulary definition.
public enum LexiconDefinitionSource: String, Codable, Hashable, Sendable {
/// Content supplied by the consuming application.
case application

/// Content authored or captured by the person using the application.
case user
}
42 changes: 42 additions & 0 deletions Sources/LexiconKit/LexiconEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation

/// A persistence-friendly vocabulary entry independent of storage and presentation frameworks.
public struct LexiconEntry: Identifiable, Codable, Hashable, Sendable {
public let id: UUID
public var assetReference: LexiconAssetReference?
public var createdAt: Date
public var definitions: [LexiconDefinition]
public var modifiedAt: Date
public var tags: Set<LexiconTag>
public var term: LexiconTerm

/// Creates a vocabulary entry.
///
/// When `modifiedAt` is omitted, it begins at the same instant as `createdAt`.
///
/// - Parameters:
/// - id: The stable entry identifier.
/// - term: The collected lexical form or phrase.
/// - definitions: The definitions associated with the term.
/// - tags: Optional host-defined classification tags.
/// - assetReference: An optional opaque reference to host-owned media.
/// - createdAt: The instant the entry was created.
/// - modifiedAt: The instant the entry was last modified.
public init(
id: UUID = UUID(),
term: LexiconTerm,
definitions: [LexiconDefinition],
tags: Set<LexiconTag> = [],
assetReference: LexiconAssetReference? = nil,
createdAt: Date = Date(),
modifiedAt: Date? = nil
) {
self.id = id
self.assetReference = assetReference
self.createdAt = createdAt
self.definitions = definitions
self.modifiedAt = modifiedAt ?? createdAt
self.tags = tags
self.term = term
}
}
23 changes: 22 additions & 1 deletion Sources/LexiconKit/LexiconKit.docc/LexiconKit.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,25 @@ Model persistence-friendly vocabulary values without coupling consumers to UI, l

## Overview

The initial bootstrap intentionally contains no speculative public API. Add capabilities only when a concrete consumer establishes their requirements and tests.
LexiconKit models vocabulary independently from persistence, synchronization, user interface, and learning progression. Hosts decide how entries are validated, deduplicated, stored, synchronized, and presented.

Create a ``LexiconEntry`` from a ``LexiconTerm`` and one or more ``LexiconDefinition`` values. Definitions retain their ``LexiconDefinitionSource`` so application-provided and user-provided content remain distinguishable after serialization. ``LexiconTag`` values and an optional ``LexiconAssetReference`` carry host-defined metadata without leaking application or media-framework types into the domain.

All public values are `Codable`, `Hashable`, and `Sendable`. Stable entry and definition identifiers plus explicit creation and modification timestamps let a host persist and merge values using its own policy.

## Topics

### Entries

- ``LexiconEntry``
- ``LexiconTerm``

### Definitions

- ``LexiconDefinition``
- ``LexiconDefinitionSource``

### Host-owned metadata

- ``LexiconTag``
- ``LexiconAssetReference``
2 changes: 0 additions & 2 deletions Sources/LexiconKit/LexiconKit.swift

This file was deleted.

13 changes: 13 additions & 0 deletions Sources/LexiconKit/LexiconTag.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

/// A host-defined vocabulary classification value.
public struct LexiconTag: RawRepresentable, Codable, Hashable, Sendable {
public let rawValue: String

/// Creates a tag without imposing application-specific validation or normalization.
///
/// - Parameter rawValue: The host-defined tag value.
public init(rawValue: String) {
self.rawValue = rawValue
}
}
17 changes: 17 additions & 0 deletions Sources/LexiconKit/LexiconTerm.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

/// A collected lexical form or phrase and its language metadata.
public struct LexiconTerm: Codable, Hashable, Sendable {
public var languageCode: String
public var text: String

/// Creates a vocabulary term.
///
/// - Parameters:
/// - text: The collected lexical form or phrase.
/// - languageCode: The BCP 47 language code for the text.
public init(text: String, languageCode: String) {
self.languageCode = languageCode
self.text = text
}
}
90 changes: 90 additions & 0 deletions Tests/LexiconKitTests/LexiconEntryTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Foundation
import Testing

@testable import LexiconKit

struct LexiconEntryTests {
@Test func completeEntryRoundTripsThroughCodable() throws {
// Given
let entryID = UUID(uuidString: "D2E60778-E44B-48BE-B539-268D1F15229A")!
let definitionID = UUID(uuidString: "707E18F0-4433-45BE-A2FA-4EE156615677")!
let createdAt = Date(timeIntervalSince1970: 1_789_300_000)
let modifiedAt = createdAt.addingTimeInterval(60)
let entry = LexiconEntry(
id: entryID,
term: LexiconTerm(text: "Haus", languageCode: "de"),
definitions: [
LexiconDefinition(
id: definitionID,
text: "house",
languageCode: "en",
source: .user
)
],
tags: [LexiconTag(rawValue: "reading")],
assetReference: LexiconAssetReference(rawValue: "images/haus"),
createdAt: createdAt,
modifiedAt: modifiedAt
)

// When
let data = try JSONEncoder().encode(entry)
let decoded = try JSONDecoder().decode(LexiconEntry.self, from: data)

// Then
#expect(decoded == entry)
#expect(decoded.id == entryID)
#expect(decoded.definitions.first?.id == definitionID)
#expect(decoded.definitions.first?.source == .user)
#expect(decoded.tags == [LexiconTag(rawValue: "reading")])
#expect(decoded.assetReference == LexiconAssetReference(rawValue: "images/haus"))
}

@Test func applicationDefinitionProvenanceRoundTrips() throws {
let definition = LexiconDefinition(
text: "a building for people to live in",
languageCode: "en",
source: .application
)

let data = try JSONEncoder().encode(definition)
let decoded = try JSONDecoder().decode(LexiconDefinition.self, from: data)

#expect(decoded == definition)
#expect(decoded.source == .application)
}

@Test func initialModifiedTimestampMatchesCreationTimestamp() {
let createdAt = Date(timeIntervalSince1970: 1_789_300_000)

let entry = LexiconEntry(
term: LexiconTerm(text: "Maus", languageCode: "de"),
definitions: [],
createdAt: createdAt
)

#expect(entry.createdAt == createdAt)
#expect(entry.modifiedAt == createdAt)
}

@Test func hostCanUpdateContentWithoutChangingIdentityOrCreationTimestamp() {
let createdAt = Date(timeIntervalSince1970: 1_789_300_000)
let modifiedAt = createdAt.addingTimeInterval(90)
var entry = LexiconEntry(
term: LexiconTerm(text: "Buch", languageCode: "de"),
definitions: [],
createdAt: createdAt
)
let originalID = entry.id

entry.definitions.append(
LexiconDefinition(text: "book", languageCode: "en", source: .user)
)
entry.modifiedAt = modifiedAt

#expect(entry.id == originalID)
#expect(entry.createdAt == createdAt)
#expect(entry.modifiedAt == modifiedAt)
#expect(entry.definitions.map(\.text) == ["book"])
}
}
7 changes: 0 additions & 7 deletions Tests/LexiconKitTests/LexiconKitTests.swift

This file was deleted.