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
21 changes: 21 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ jobs:
build test \
CODE_SIGNING_ALLOWED=NO

- name: Build + test meeting-notes macOS
working-directory: apps/meeting-notes/macos-swift
run: |
xcodebuild \
-scheme MeetingNotesMac \
-destination 'platform=macOS' \
build test \
CODE_SIGNING_ALLOWED=NO

native-windows:
runs-on: windows-latest
steps:
Expand All @@ -100,6 +109,12 @@ jobs:
dotnet restore DocApproval.Tests/DocApproval.Tests.csproj
dotnet test DocApproval.Tests/DocApproval.Tests.csproj -c Release --nologo

- name: Restore + test meeting-notes WinUI logic
working-directory: apps/meeting-notes/windows-winui
run: |
dotnet restore MeetingNotes.Tests/MeetingNotes.Tests.csproj
dotnet test MeetingNotes.Tests/MeetingNotes.Tests.csproj -c Release --nologo

# Required: GTK shells (AdwApplicationWindowExt / GString fixes landed).
native-linux-gtk:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -168,3 +183,9 @@ jobs:
env:
TRAVERSE_REPO: ${{ github.workspace }}/.traverse-checkout
run: ./gradlew --no-daemon testDebugUnitTest

- name: Gradle test meeting-notes
working-directory: apps/meeting-notes/android-compose
env:
TRAVERSE_REPO: ${{ github.workspace }}/.traverse-checkout
run: ./gradlew --no-daemon testDebugUnitTest
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ gh project item-list 2 --owner traverse-framework --format json --limit 300 \
| `phase2-sidecar-nightly` | Future | Defer — low demo value |
| `embed-trace-explorer` | **Done** (#225) | Embedded Trace API in Trace Explorer web |
| `registry-ref-starter-process` | **Done** (#224) | Process component uses `registry_ref` |
| `registry-ref-full-kit-cutover` | **In Progress** | All six primary components use `registry_ref` |
| `registry-ref-full-kit-cutover` | **Done** (#235) | All six primary components use `registry_ref` |
| `meeting-notes-wave2-os-ports` | **In Progress** | iOS + macOS + Windows + Android meeting-notes embeds |
| `consume-product-wasm-agents` | **Done** (#227) | Digest-pinned Traverse-published starter agents |

Full gap table + wave notes: [`docs/production-reference-plan.md`](docs/production-reference-plan.md).
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ UI examples for [Traverse](https://github.com/traverse-framework/Traverse).
|---|---|---|
| **traverse-starter** | Submit a short note → title, tags, note type, next action, status | [web](apps/traverse-starter/web-react/) · [all OS](#by-os--target) |
| **doc-approval** | Paste a document → type, parties, amounts, confidence, recommendation | [web](apps/doc-approval/web-react/) |
| **meeting-notes** | Paste a transcript → action items, decisions, follow-ups, summary | [web](apps/meeting-notes/web-react/) |
| **meeting-notes** | Paste a transcript → action items, decisions, follow-ups, summary | [web](apps/meeting-notes/web-react/) · [all OS](#by-os--target) |
| **trace-explorer** | Browse execution traces (debugger — not a product shell to copy) | [web](apps/trace-explorer/web-react/) |

**Extra demos / kits** — useful samples, **not** the production pattern to copy (prefer traverse-starter / doc-approval / meeting-notes):
Expand Down Expand Up @@ -54,7 +54,7 @@ Same apps on other platforms (`—` = not shipped yet):
|---|---|---|---|---|---|---|---|
| traverse-starter | [link](apps/traverse-starter/web-react/) | [link](apps/traverse-starter/macos-swift/) | [link](apps/traverse-starter/ios-swift/) | [link](apps/traverse-starter/android-compose/) | [link](apps/traverse-starter/windows-winui/) | [link](apps/traverse-starter/linux-gtk/) | [link](apps/traverse-starter/cli-rust/) |
| doc-approval | [link](apps/doc-approval/web-react/) | [link](apps/doc-approval/macos-swift/) | [link](apps/doc-approval/ios-swift/) | [link](apps/doc-approval/android-compose/) | [link](apps/doc-approval/windows-winui/) | [link](apps/doc-approval/linux-gtk/) | [link](apps/doc-approval/cli-rust/) |
| meeting-notes | [link](apps/meeting-notes/web-react/) | | | | | [link](apps/meeting-notes/linux-gtk/) | [link](apps/meeting-notes/cli-rust/) |
| meeting-notes | [link](apps/meeting-notes/web-react/) | [link](apps/meeting-notes/macos-swift/) | [link](apps/meeting-notes/ios-swift/) | [link](apps/meeting-notes/android-compose/) | [link](apps/meeting-notes/windows-winui/) | [link](apps/meeting-notes/linux-gtk/) | [link](apps/meeting-notes/cli-rust/) |

---

Expand Down
3 changes: 3 additions & 0 deletions apps/meeting-notes/MeetingNotesCore/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.build/
.swiftpm/
Package.resolved
28 changes: 28 additions & 0 deletions apps/meeting-notes/MeetingNotesCore/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version: 6.0
import PackageDescription

let package = Package(
name: "MeetingNotesCore",
platforms: [
.iOS(.v17),
.macOS(.v14),
],
products: [
.library(name: "MeetingNotesCore", targets: ["MeetingNotesCore"]),
],
dependencies: [
.package(path: "../../../vendor/traverse-embedder-swift"),
],
targets: [
.target(
name: "MeetingNotesCore",
dependencies: [
.product(name: "TraverseEmbedder", package: "traverse-embedder-swift"),
]
),
.testTarget(
name: "MeetingNotesCoreTests",
dependencies: ["MeetingNotesCore"]
),
]
)
11 changes: 11 additions & 0 deletions apps/meeting-notes/MeetingNotesCore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# MeetingNotesCore

Shared Swift package for meeting-notes iOS and macOS shells.

- `EmbeddedHost` — `RuntimeTraverseEmbedder` / `InMemoryTraverseEmbedder` boundary
- `AppStateViewModel` — Zone 1 Ready/Unavailable + submit/reset
- `MeetingNotesOutput` — runtime-owned field decoding only

```bash
cd apps/meeting-notes/MeetingNotesCore && swift test
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Combine
import Foundation

public enum RuntimeStatus: Equatable, Sendable {
case starting
case ready
case unavailable
}

/// Drives UI state from an embedded Traverse host.
/// Contains no local business-field computation.
@MainActor
public final class AppStateViewModel: ObservableObject {
@Published public private(set) var currentState: String = "idle"
@Published public private(set) var output: MeetingNotesOutput?
@Published public private(set) var errorMessage: String?
@Published public private(set) var sessionId: String?
@Published public private(set) var trace: [TraceEvent] = []
@Published public private(set) var runtimeStatus: RuntimeStatus = .starting
@Published public private(set) var submitting: Bool = false
@Published public var transcript: String = ""
@Published public var showTrace: Bool = false

public let appId: String
public let transcriptMaxLength: Int
public let runtimeMode: String
public let workflowId: String
public private(set) var workspaceId: String

private let host: EmbeddedHostProtocol?

public init(
host: EmbeddedHostProtocol?,
workspaceId: String,
appId: String = EmbeddedHost.defaultAppId,
transcriptMaxLength: Int = 5_000
) {
self.host = host
self.workspaceId = workspaceId
self.appId = appId
self.transcriptMaxLength = transcriptMaxLength
self.runtimeMode = EmbeddedHost.runtimeModeEmbedded
self.workflowId = host?.workflowId ?? EmbeddedHost.defaultWorkflowId
self.runtimeStatus = host?.isReady == true ? .ready : .unavailable
}

public var canSubmit: Bool {
runtimeStatus == .ready &&
!transcript.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
!isRunning
}

public var isRunning: Bool {
submitting || currentState == "processing"
}

public func updateWorkspace(_ workspaceId: String) {
self.workspaceId = workspaceId
}

public func refreshRuntimeStatus() {
runtimeStatus = host?.isReady == true ? .ready : .unavailable
}

public func submit() {
guard canSubmit, let host else { return }
let trimmed = String(transcript.trimmingCharacters(in: .whitespacesAndNewlines).prefix(transcriptMaxLength))
submitting = true
currentState = "processing"
errorMessage = nil
output = nil
trace = []
showTrace = false
sessionId = nil

Task { [weak self] in
guard let self else { return }
do {
let result = try await Task.detached {
try host.submitTranscript(trimmed)
}.value
await MainActor.run {
self.sessionId = result.sessionId
self.trace = result.events
self.showTrace = !result.events.isEmpty
self.submitting = false
if let error = result.error {
self.currentState = "error"
self.errorMessage = error
} else {
self.output = result.output ?? .empty
self.currentState = "completed"
}
}
} catch {
await MainActor.run {
self.submitting = false
self.currentState = "error"
self.errorMessage = error.localizedDescription
}
}
}
}

public func reset() {
submitting = false
currentState = "idle"
sessionId = nil
output = nil
trace = []
errorMessage = nil
showTrace = false
}

/// Compatibility alias for shell call sites.
public func resetLocal() {
reset()
}
}
Loading
Loading