summaryrefslogtreecommitdiff
path: root/makima/ios/Sources/Makima/Net/DemoSession.swift
blob: 3d14a64f88d896ee34c145c9d209bd1d8c4955fc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import Foundation

/// In-process URLSession that returns canned demo payloads for screenshot
/// builds. Never hits the network. Used only when SCREENSHOT_MODE is on.
enum DemoSession {
    static func make() -> URLSession {
        let cfg = URLSessionConfiguration.ephemeral
        cfg.protocolClasses = [DemoURLProtocol.self] + (cfg.protocolClasses ?? [])
        return URLSession(configuration: cfg)
    }
}

final class DemoURLProtocol: URLProtocol, @unchecked Sendable {
    override class func canInit(with request: URLRequest) -> Bool {
        request.url?.host == "makima.jp"
    }
    override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }

    override func startLoading() {
        let (status, body) = DemoPayloads.response(for: request)
        let response = HTTPURLResponse(
            url: request.url!,
            statusCode: status,
            httpVersion: "HTTP/1.1",
            headerFields: ["Content-Type": "application/json"]
        )!
        client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
        client?.urlProtocol(self, didLoad: body)
        client?.urlProtocolDidFinishLoading(self)
    }
    override func stopLoading() {}
}

enum DemoPayloads {
    static func response(for request: URLRequest) -> (Int, Data) {
        let path = request.url?.path ?? ""
        switch path {
        case let p where p.hasSuffix("/mesh/daemons"):
            return (200, data(daemonsJSON))
        case let p where p.hasSuffix("/mesh/tasks"):
            return (200, data(tasksJSON))
        case let p where p.hasSuffix("/contracts"):
            return (200, data(contractsJSON))
        case let p where p.hasSuffix("/directives"):
            return (200, data(directivesJSON))
        case let p where p.hasSuffix("/listen/sessions"):
            return (200, data(listenJSON))
        case let p where p.contains("/mesh/tasks/") && p.hasSuffix("/output"):
            return (200, data(taskOutputJSON))
        case let p where p.contains("/mesh/tasks/"):
            return (200, data(taskDetailJSON))
        default:
            return (200, data(#"{"items":[],"total":0}"#))
        }
    }

    private static func data(_ s: String) -> Data { Data(s.utf8) }

    static let daemonsJSON = #"""
    {
      "daemons": [
        {"id":"d1","status":"online","hostname":"soryu-alpha","maxConcurrentTasks":4,"currentTaskCount":2,"lastHeartbeatAt":"2026-04-24T10:42:00Z"},
        {"id":"d2","status":"online","hostname":"soryu-beta","maxConcurrentTasks":4,"currentTaskCount":1,"lastHeartbeatAt":"2026-04-24T10:42:10Z"},
        {"id":"d3","status":"offline","hostname":"isolated-box","maxConcurrentTasks":2,"currentTaskCount":0,"lastHeartbeatAt":"2026-04-23T19:10:00Z"}
      ],
      "total": 3
    }
    """#

    static let contractsJSON = #"""
    {
      "contracts": [
        {"id":"c1","name":"Ingest pipeline rework","description":"Migrate transcript ingest from SSE to WS","contractType":"specification","phase":"execute","status":"active","autonomousLoop":true,"phaseGuard":false,"localOnly":false,"version":3,"updatedAt":"2026-04-24T10:30:00Z"},
        {"id":"c2","name":"Dashboard layout v2","description":"Port masthead + nav to dense mode","contractType":"simple","phase":"plan","status":"active","autonomousLoop":false,"phaseGuard":true,"updatedAt":"2026-04-24T09:15:00Z"},
        {"id":"c3","name":"Makima iOS app","description":"Native client shipping M0-M8","contractType":"specification","phase":"review","status":"active","updatedAt":"2026-04-24T10:50:00Z"},
        {"id":"c4","name":"Archive Q1 traces","description":"Cold-store transcripts >90d","contractType":"simple","phase":"done","status":"completed","updatedAt":"2026-03-30T17:00:00Z"}
      ],
      "total": 4
    }
    """#

    static let tasksJSON = #"""
    {
      "tasks": [
        {"id":"t1","contractId":"c3","name":"Implement HomeStore + cards","status":"done","priority":2,"plan":"Wire parallel fetch, render 5 cards","daemonId":"d1","progressSummary":"All cards render, live refresh working","updatedAt":"2026-04-24T10:48:00Z","lastOutput":"Implementation complete. Running tests…\n\n```swift\nasync let contractsTask = tryGet(\"/contracts\")\nasync let daemonsTask = tryGet(\"/mesh/daemons\")\n```\n\n<COMPLETION_GATE>\nready: true\nreason: \"All cards render live data; pull-to-refresh verified\"\nprogress: \"Home composite landed in 4 files\"\n</COMPLETION_GATE>"},
        {"id":"t2","contractId":"c3","name":"WebSocket livestream","status":"running","priority":2,"plan":"Implement TaskWebSocket + event merging","daemonId":"d1","progressSummary":"Handler wiring in progress","updatedAt":"2026-04-24T10:51:00Z","lastOutput":"Connecting to wss://makima.jp/api/v1/mesh/tasks/subscribe…\nSubscribed to all tasks."},
        {"id":"t3","contractId":"c3","name":"Markdown renderer","status":"done","priority":1,"daemonId":"d2","updatedAt":"2026-04-24T10:35:00Z"},
        {"id":"t4","contractId":"c1","name":"SSE->WS migration probe","status":"blocked","priority":3,"daemonId":"d2","errorMessage":"Needs schema review from supervisor","updatedAt":"2026-04-24T09:02:00Z"},
        {"id":"t5","contractId":"c2","name":"Dense masthead spec","status":"pending","priority":1,"updatedAt":"2026-04-24T08:00:00Z"}
      ],
      "total": 5
    }
    """#

    static let directivesJSON = #"""
    {
      "directives": [
        {"id":"dir1","name":"Schema review required","goal":"Approve SSE->WS schema change for transcript events","status":"pending","updatedAt":"2026-04-24T10:45:00Z"},
        {"id":"dir2","name":"Choose archive tier","goal":"S3 Glacier vs R2 cold for Q1 traces","status":"pending","updatedAt":"2026-04-24T10:12:00Z"},
        {"id":"dir3","name":"Approve deploy window","goal":"Ship iOS v1 build to TestFlight tonight 22:00 BST","status":"pending","updatedAt":"2026-04-24T09:55:00Z"}
      ],
      "total": 3
    }
    """#

    static let listenJSON = #"""
    {
      "items": [
        {"id":"l1","title":"Dispatch — ridge sector","startedAt":"2026-04-24T10:10:00Z","endedAt":"2026-04-24T10:31:00Z","transcriptPreview":"…rotated assets to sector 7, mark-two, maintaining comms on channel…","duration":1260}
      ],
      "total": 1
    }
    """#

    static let taskDetailJSON = #"""
    {"id":"t2","contractId":"c3","name":"WebSocket livestream","status":"running","priority":2,"plan":"Implement TaskWebSocket + event merging","daemonId":"d1","progressSummary":"Handler wiring in progress","updatedAt":"2026-04-24T10:51:00Z","lastOutput":""}
    """#

    static let taskOutputJSON = #"""
    {"output":"Connecting to wss://makima.jp/api/v1/mesh/tasks/subscribe…\n\nSubscribed to all task updates.\n\n```swift\nfunc subscribe(taskId: String) {\n    send(.subscribe(taskId: taskId))\n    send(.subscribeOutput(taskId: taskId))\n}\n```\n\nReceived first `taskOutput` event for task `t2` (messageType=assistant).\n\n<COMPLETION_GATE>\nready: false\nreason: \"Awaiting reconnect test pass\"\nprogress: \"Protocol parity verified against mesh_ws.rs; reconnect backoff in progress\"\nblockers: \"reconnect not yet tested end-to-end, resync frame TBD\"\n</COMPLETION_GATE>","truncated":false}
    """#
}