import XCTest /// The one narrow mobile behavior `BoardSession` adds beyond `BoardStore`'s own machinery (this /// card, ruled 2026-08-09): a session opened against a board with no `CLAUDE.md` at all — the /// shape every board predating the "install at creation" fix is left in, and the shape this /// suite's own fixture is deliberately in — gets one written the moment its board screen /// appears, with no dependency on the board ever touching a Mac. /// /// This is the one piece of that wiring `KanbanTests` cannot reach: the write itself /// (`AgentGuide.install`) is the Mac's own well-covered code (`AgentGuideTests.swift`), and /// `BoardWriterCreateBoardTests.createBoardInstallsTheCurrentAgentGuide` already proves /// `BoardWriter.createBoard` — the one path `KanbanMobile.BoardIndexStore.createBoard` also /// calls — installs a current guide at birth. What only exists inside the `KanbanMobile` module, /// and so can only be driven end to end from here, is `BoardSession.open()` firing the one-shot /// `refreshAgentGuideOnce()` for a board that predates that guarantee. `KanbanMobileUITests` /// black-boxes the app (plain `XCTest`, no `@testable import KanbanMobile`), so this asserts on /// the marker prefix rather than `AgentGuide.version` — a version bump does not need to touch /// this file, and the KanbanTests suite is what pins the exact version and byte content. final class AgentGuideUITests: XCTestCase { /// Boards list → tap the fixture board → its lane screen appears — no need to drill into a /// lane or a card, which is what keeps this the cheapest possible proof of the wiring rather /// than a rerun of `navigateToFirstCard`'s own coverage: `BoardScreen`'s /// `.task { session.open() }` is what fires the refresh, and it fires the moment the lane /// list itself appears. @MainActor func testOpeningABoardWithNoGuideInstallsOne() throws { let (app, root) = XCUIApplication.launchedWithFixtureBoard() let boardRow = app.element(labelContaining: RichBoard.title) XCTAssertTrue( boardRow.waitForExistence(timeout: XCUIApplication.uiTimeout), "the \"\(RichBoard.title)\" row never appeared — check the fixture copy or the first scan" ) boardRow.tap() XCTAssertTrue( waitForAgentGuide(under: root), "no CLAUDE.md carrying the app's marker ever appeared under \(root.path)" ) } } /// `waitForFile(under:containing:)` only ever looks at files named `index.md` (every other test /// in this bundle is asserting on a card's or a board's own content) — wrong shape for a file /// named `CLAUDE.md`, so this polls the one path a guide install can land on directly instead of /// walking the tree. Same 0.25s-poll shape as its sibling, for the same reason: the write reaches /// disk asynchronously, well after `BoardScreen`'s `.task { session.open() }` returns. private func waitForAgentGuide(under root: URL, timeout: TimeInterval = 15) -> Bool { let guideURL = root .appendingPathComponent(RichBoard.packageName, isDirectory: true) .appendingPathComponent("CLAUDE.md") let deadline = Date().addingTimeInterval(timeout) repeat { if let text = try? String(contentsOf: guideURL, encoding: .utf8), text.contains("lanework-agent-guide v") { return true } RunLoop.current.run(until: Date().addingTimeInterval(0.25)) } while Date() < deadline return false }