<!-- https://ios.app.promptugal.news/admin/briefs/2026-11-08__three-apps/status/team.html · PrompTugal for iOS 0.4.106 -->

# Team session: PTWeb and PTAgenda

> Summit priority (freeze 16:00 UTC): head d310767 is in split/rc 75203d5 (conductor's full run). Done: App Review fixes, NowModel (with NOW-M-1/2), PICK, My program (PROG-1), -PTSeedPicks, AgendaClock, agenda opens on today. Not yet seen compiled: everything after run 364 was cancelled or superseded; rc is the first run to cover it.

**Branch:** `split/team` (merged origin/dev b845004, phase 1 + vault) · **Updated:** 8 Nov, 12:25 UTC

## Account B notes (8 Nov, about 01:50 UTC)

- Merged origin/dev (docs only). Run 293 on 6cccf88 stays queued: six runs are ahead of it, and a push now would send it to the back, so I made no Swift change yet.
- Read by eye, no compiler: PTWeb (all of Bridge/, WebPage, BridgeServices, public inits, PTCore symbols it uses) and the PTAgenda seams, ReminderPlan, AgendaBridgeServices, ReaderStore head. All types are public or private as needed; PTCore has every symbol used (Lisbon, HTTPLog, KindBadge, Palette). Nothing to fix before the first compile.
- One behaviour bug to fix with the `AppLinks.scheme` change: `ReminderDelegate` (ReminderScheduler.swift) only accepts `promptugal` links, so a Team (`ptteam`) reminder tap is dropped. It will check `AppLinks.scheme`.
- `AppLinks` is not on dev yet (platform ce3602e, run 292 queued).

- **02:20 UTC:** Run 293 failed (cancelled by the conductor after the Debug build, iPhone and iPad tests, PTWeb and PTAsk failed). The log has one real error, twice: `BridgeScripts.swift` used `ScheduleDocument.storageKey` and `.changeEvent`, and PTWeb cannot see PTAgenda. PTWeb now owns both strings (`BridgeScripts.scheduleKey`, `scheduleChangeEvent`) and `ScheduleDocument` reads them. PTAsk failed through PTWeb, so the same fix should clear it. `ReminderDelegate` now accepts `promptugal` and `ptteam` links. Pushed as 115ba30; run 296 (app=all) queued. PTAgenda itself has not compiled yet.

- **03:20 UTC:** 17a160c merged to dev as 7863ae2. Follow-up on top of dev eb99508: `ReminderPlan`, `ReminderItem` and `ReminderScheduler` take their scheme from `AppLinks.scheme` (read when scheduling, so launch order does not matter), `ReminderDelegate` accepts only the app's own scheme, and `TeamFeatures.urlScheme` is gone. `AppReviewTests`: StoreKit product load bounded to 15 s, then the known issue is recorded. Not yet compiled.

- **04:15 UTC:** Run 313 (macos-26, app=all) on 3899bb1 green, all 15 jobs. READY sent to the conductor.

- **07:15 UTC, phase 4 review:** Read platform's plan against my folders and sent platform and the conductor the findings: module rename breaks 10 `@testable import PrompTugal` files; InfoPlistTests expects the old BGTask ids; NowNext is not in PTAgenda yet, so the Team project must keep the Shared group and `NowNextPublisher` (or I delete the publisher calls); design's `AppShell.swift:97` must compare with `AppLinks.scheme`. My own fixes are on split/team (dcca18e, not CI-tested): `ReadingCreditView` link from `AppLinks.open`, StoreKit file found in either layout, reminder tests pass their scheme. Waiting for platform's move to review it.

- **07:25 UTC, NowNext moved (conductor ruling):** `Shared/NowNext.swift` is now `Packages/PTAgenda/Sources/PTAgenda/NowNext.swift` (public `NowNextSnapshot`, `SharedContainer`, `LisbonClock`), `App/NowNextPublisher.swift` is `Packages/PTAgenda/Sources/PTAgenda/NowNextPublisher.swift` (public, no team dependency), and `NowNextTests.swift` is in `Packages/PTAgenda/Tests/PTAgendaTests/`. `SharedContainer.appGroup` is now a settable static (default `group.news.promptugal.reader`; an app with its own group sets it at launch). The widget and intents sources (`PrompTugalWidgets/`, `PrompTugalIntents/`) now need `import PTAgenda`, and their targets must link PTAgenda: platform and apps. Not CI-tested.

- **11:00 UTC, round 2 (contracts §8): native Team screens.** All in `Apps/Team/Sources/Team/`, read through `TeamStore` from the team site's public data, kept on the device, web page one tap away.
  - `TeamStandupAgenda.swift` (parsers), `StandupsView.swift` (`StandupsView`, `StandupDetailView`, items): list with open counts, agenda by kind, "only items for me", briefings.
  - `MyCommandCentreView.swift` (`MyCommandCentreView`, `CentreIssuesView`): decisions, issues with filters, workstreams, agents, activity, messages, sessions.
  - `TeamPage.swift`, `TeamPageView.swift`: any twin page as native blocks (reports, board health, pulse summary); `ReportsView`, `WakesView`, `PulseView`.
  - `TeamStore`: `standupSummaries`, `agendas`, `pages`, `refreshAgenda(slot:)`, `refreshPage(_:)`, validated paths, offline cache. Tests: `Apps/Team/Tests/TeamNativeTests.swift`.
  - **Not registered:** design owns `Shell/Destinations.swift`. Entries to add once green: `team.commandcentre` → `MyCommandCentreView()` (kind native), `team.standups` → `StandupsView()`, `agents.reports` → `ReportsView()`, `agents.wakes` → `WakesView()`, `briefings.pulse` → `PulseView()`, `briefings.board` → `TeamPageView(path: "board-health", title: "Board health")`.
- **PTNowNext (conductor request):** `Packages/PTNowNext` (no dependencies) holds `NowNextSnapshot`, `SharedContainer`, `LisbonClock`; PTAgenda depends on it and re-exports it; `NowNextPublisher` stays in PTAgenda. In `apps.json` and the workspace. **Exception to the project-ownership rule, approved by the conductor:** I added a reference-only `XCLocalSwiftPackageReference` for `../../Packages/PTNowNext` to `Apps/Team/Team.xcodeproj` (platform told). `PrompTugal.xcodeproj` is apps': its reference and the widgets and intents product links come with their merge.

- **11:30 UTC, summit app (summit-app-store brief):** conductor paused Team-native; my job is PTAgenda for participants.
  - **`NowBoard`** (`Packages/PTAgenda/Sources/PTAgenda/Schedule/NowBoard.swift`): `NowBoard.snapshot(agenda:picks:now:)` gives venue now/next, my now/next (a day ahead), clashes among my picks (`DayPlanner.clashes(in:)`, extracted from the planner), per-stage now/next, and the summit phase (`noAgenda`, `before`, `live`, `between`, `ended`). My program is built from the picks' own event copies, so it shows even before the agenda loads. `ReaderStore.nowBoard(at:)` feeds it from the screen's one-minute timeline.
  - **Offline agenda:** `AgendaHub.seedAgenda` (the app sets it from the core pack at launch) is read when nothing is cached; `ReaderStore.agendaSource` (`none`, `seed`, `cache`, `network`) and `agendaUpdated` say where the agenda came from, for "synced 3 min ago".
  - **Reminders** already work for picks with no team (`ReminderScheduler(team: nil, identity: nil)`); clashes already feed the planner. Tests: `NowBoardTests`, `NowNextSnapshotTests` (PTNowNext).
  - **Waiting for:** ux's spec with screen ids. Needs from apps: PrompTugal.xcodeproj reference for PTNowNext and the widgets/intents links; from collab: set `AgendaHub.seedAgenda` from the core pack.

- **12:25 UTC, summit app (PTAgenda, all on split/team, uncompiled until rc reports):**
  - `NowModel` (`Schedule/NowModel.swift`, pure): phase, For you (with clash flag), On now, Up next, Tomorrow, countdown, first items. NOW-M-1: first items are the first `registration` and the first `summit` item (Opening Night); NOW-M-2: registration never takes an On now, Up next or Tomorrow slot. `ReaderStore.nowModel(at:)` feeds it.
  - PICK: `PickRules`, `PickFeedback` (toast, clash toast with View), `PickStarButton(event:)`, `.pickToasts()`, `NowNextRepublisher(schedule:)`. App wiring (design): `.pickToasts()` at the root, one `ScheduleStore` per app, `PickStarButton` on every session row.
  - My program (PROG-1): `ProgramModel` (clash groups, free gaps of 30 min or more, day counts, `defaultDay`) and `MyProgramView(chooseTalks:open:)`; needs `ScheduleStore`, `ReminderScheduler` and an optional `PickFeedback` in the environment. Route id `team.schedule` is kept.
  - `AgendaView(openSession:)`: the app's native session detail (deck's PTExplore detail, per the conductor). The agenda opens on today (Lisbon) during the summit.
  - `PickSeed` / `ScheduleStore.seedPicks(arguments:)`: DEBUG-only `-PTSeedPicks`, slugs or full ids. `AgendaClock.override` (set from `-PTNow` in DEBUG): the one clock of the agenda screens.
  - App Review: PTAgenda text has no Web Summit trademark; `WebsiteSources.resource` selects `WebsitesPublic.json` (the public app sets it and removes the old shortcut asset).
  - Waiting for: i18n's merge, then I switch my new strings to `String(localized:bundle:.module)`; design's registration of the screens; rc results.

## Handoff

**Handed off at 01:35 UTC, 8 Nov, to the session on account B.**

- **Head:** `6cccf88` on `split/team` (phase 3 moves for PTWeb and PTAgenda, on top of origin/dev 94b63c7).
- **Last CI run:** ios.yml run 293 (`workflow_dispatch`, app=all) on 6cccf88. It was still queued when I stopped. Result not seen. Read it first: `actions_list` for ios.yml, branch split/team.
- **Done:**
  - PTWeb: `Bridge/`, `WebPage`, `WebsiteView`, `WebsiteShortcuts`, `Websites.json` (package resource, `Bundle.module`). Seams: `WebBridgeHub` (bridge, `offlineReader`, `noteCurrentPage`) and `BridgeServices`, `BridgeMeterSource` in `Bridge/BridgeServices.swift`.
  - PTAgenda: `Schedule/` (not `ScheduleIntents.swift`), `Notifications/`, `AgendaView`, `ReaderStore` (in `News/`). Seams in `AgendaSources.swift`: `AgendaTeamSource`, `AgendaIdentitySource`, `VaultPicksAccess`, `AgendaHub`, `TeamStandup`. `AgendaBridgeServices` is the adapter for the bridge.
  - Team app glue: `Team/TeamAgendaConformances.swift` (conformances and `VaultSessionPicks`), `Team/TeamFeatures.swift` (`registerHubs()`).
  - Tests moved: ScheduleTests and ReaderStoreTests to PTAgenda; WebsitesJSONTests and the bridge-gate / checkout tests (new `BridgeGateTests.swift`) to PTWeb.
  - Both `Package.swift` files use `swiftLanguageModes: [.v5]`, matching the app (SWIFT_VERSION 5.0). PTWeb has `resources: [.process("Resources")]`.
- **Half-done / unknown:**
  - Nothing has been compiled. Expect errors: I added `public` with a script (`publicize.py`, not in the repo) and generated public inits for structs without one. Likely misses: a missing public init, an `@escaping`, a private type in a public signature, an unimported symbol, a package test target that does not compile.
  - Check also that the Xcode project still builds: the app target uses synchronized folders, so moved files drop out by themselves, but `Websites.json` and `Shared/` are worth a look.
- **Exact next steps:**
  1. Read run 293 (`get_job_logs` with `failed_only`). Fix, push, dispatch ios.yml again (`app=all`; pushes to split/** do not trigger it).
  2. Switch the reminder URL scheme to `AppLinks.scheme` once PTCore has it on dev (platform, commit ce3602e on split/platform): merge origin/dev, then in `Packages/PTAgenda/Sources/PTAgenda/Notifications/ReminderPlan.swift` replace the default `"promptugal"` (`ReminderItem.scheme`, `ReminderPlan.items(scheme:)`) and in `ReminderScheduler.init(urlScheme:)` use `AppLinks.scheme`. Then `TeamFeatures.urlScheme` can go.
  3. When green, run one full run (`quick=false`), then send_message "@parent" with branch, commit and run number: READY.
  4. `Shared/NowNext.swift` stays until the widget target links PTAgenda (phase 4, platform). `ScheduleIntents.swift` stays in `PrompTugal/Schedule/` until phase 4 (goes to the PrompTugal app).
- **Open questions:**
  - Is `swiftLanguageModes: [.v5]` right for all packages? The other packages on dev do not set it.
  - `WebBridgeHub.noteCurrentPage` is wired to `CollabHooks.noteCurrentPage` in `TeamFeatures.registerHubs()`. The public app needs its own wiring for Ask's "current page".
  - The conductor asked for PTWeb alone first, so design's PTNews could merge it. It is in the same commit as PTAgenda. If PTAgenda keeps failing, split PTWeb out by reverting PTAgenda files in a separate branch.

## State

- **Done:** read both briefs, mapped every type in the moving folders.
- **In progress:** this plan.
- **Next:** wait for "PTCore is on dev", then merge `origin/dev` and move.
- **Blocked:** nothing yet. Three questions below.
- **Last CI run:** none (no code changed).

## What moves

| Package | Files | Lines |
| --- | --- | --- |
| PTWeb | `Bridge/` (CheckoutPolicy, AppBridge, BridgeScripts, SiteDataStores, BridgeLog, BridgeLogView), `Shell/WebPage.swift`, `App/Web/` (WebsiteView, WebsiteShortcuts), `App/Resources/Websites.json` | 736 + 244 + 440 |
| PTAgenda | `Schedule/` (14 files), `Notifications/` (3 files), `Shared/NowNext.swift` | 1783 + 348 + about 80 |

`NowNextPublisher.swift` stays in `App/` (platform). `PrompTugalWidgets/NowNextWidget.swift` also uses `NowNext.swift`, so the widget target must link PTAgenda (platform, phase 4).

## Edges that point up or sideways, and how each is broken

### PTWeb (may use only PTCore)

| Where | Uses | Fix |
| --- | --- | --- |
| `WebPage`, `WebsiteView` | `TeamFeatures.shared.bridge` (configure, attach, handlesNavigation, pageDidFinish) | `AppBridge` moves into PTWeb. Add `WebBridgeHub` in PTWeb: `@MainActor public static var bridge: AppBridge?`. The app sets it once at launch. The views call `WebBridgeHub.bridge?...`. No schedule or meter type is needed there. |
| `AppBridge` | `ScheduleStore` (document, merge, markSynced, `.scheduleDidChange`) | New `public protocol BridgeScheduleSource` in PTWeb. It works on JSON (`[String: Any]` / `Data`), so `ScheduleDocument` stays in PTAgenda. PTAgenda gets a small adapter that wraps `ScheduleStore`. See question 1. |
| `AppBridge` | `MeterStore` (update, snapshot, `.meterHasPendingCredit`) | New `public protocol BridgeMeterSource` (update(from:), balanceText, pending-credit notification name). `MeterStore` stays in the Team app and conforms. |
| `AppBridge` | `TeamIdentityStore` (current, verified, `.teamIdentityDidChange`) | New `public protocol BridgeIdentitySource` (id, name, verified). Team app conforms. |
| `AppBridge` | `ReminderScheduler` (currentAuthorization, reschedule) | New `public protocol BridgeRemindersSource`. PTAgenda's `ReminderScheduler` conforms (the app or PTAgenda adds the conformance). |
| `AppBridge` | `ReadingCredit.isAvailable` (Store/, stays in the Team app) | A `Bool` / closure `offersAppTopUp` in the init. The Team app passes `ReadingCredit.isAvailable`, the PrompTugal app passes `false`. |
| `WebPage` | `HTTPClient.redact` | PTCore (platform, phase 2). Just `import PTCore`. |
| `BridgeLog*`, `Lisbon` | PTCore | `import PTCore`. |

These sources can be one protocol (`BridgeServices`) if you prefer. I will use one protocol with five groups, so `AppBridge.init` takes one `services:` argument instead of five.

### PTAgenda (may use PTCore and PTWeb)

| Where | Uses | Fix |
| --- | --- | --- |
| `Event` (in `App/Content.swift`) | used by ScheduleStore, DayPlanner, ScheduleIntents, NowNext. Needs `FeedClient.site` and `Lisbon`. | Must be in PTCore, or PTAgenda cannot compile. See question 2. |
| `ReminderScheduler`, `ReminderSettingsView`, `PlannerStore`, `MyDayView`, `NextHoursCard`, `PlannerView` | `TeamStore` (standups, upcomingStandups, me, centres, refresh, refreshCentre) and `TeamIdentityStore` (current) | Team stays in the Team app. In PTAgenda: `public protocol AgendaTeamSource` (standups, upcomingStandups(after:), refresh(person:)) and `public protocol AgendaIdentitySource` (current person id). Move the value type `TeamStandup` (about 30 lines, only Foundation and `Lisbon`) into PTAgenda. Its `url` uses `TeamEndpoints.base`, so that URL is passed in or computed by the Team app. The planner's team-coordination part (`centres`, `TeamPerson.order`, `CommandCentre`) is reached through one closure `coordination` and one closure `loadTeam` on `PlannerStore`. Views read the sources from the environment as before. `@Environment(TeamStore.self)` becomes an environment key for the protocol. |
| `ScheduleVaultLink`, `ScheduleVaultSync` | `VaultSession.shared` (reader, canPush, readText, loadPicks, savePicks). PTAgenda does not depend on PTVaultUI. | `public protocol VaultPicksAccess` in PTAgenda (hasVault, canSave, loadPicks, savePicks, an observation hook). The app implements it over `VaultSession.shared`. `ScheduleVaultLink` takes it in `init`. It also takes a `BridgeLog`, which is in PTWeb, so that is fine. |
| `ScheduleIntents` | `TeamFeatures.shared.planner / .schedule` | `AgendaHub` in PTAgenda: `@MainActor public static var planner / schedule`, set by the app. See question 3 about App Intents in a package. |
| `MyDayView` | `AppBridge` (bridge in the environment) | Fine. PTAgenda depends on PTWeb. |
| `Notifications/ReminderPlan` and the `promptugal://open/team.schedule` link | route string only | No type. Stays as a string. |

### Stays in the Team app (team owns)

`TeamFeatures` stays in `Team/`. It builds the stores, sets `WebBridgeHub.bridge` and `AgendaHub`, and conforms the stores to the new protocols. Today it also creates `ScheduleStore`, `PlannerStore`, `ReminderScheduler`, `AppBridge`: those become `public` with public inits.

## Resources

- `Websites.json` moves to `Packages/PTWeb/Sources/PTWeb/Resources/Websites.json`, declared in `Package.swift` with `resources: [.process("Resources")]`. `WebsiteShortcuts` changes `Bundle.main` to `Bundle.module`.
- The injected JS is inline Swift strings in `BridgeScripts.swift` (no `.js` files). Nothing to bundle.
- `WebsitesJSONTests` reads `Bundle.main`. It moves to `Packages/PTWeb/Tests` and uses `Bundle.module` through `@testable import PTWeb`.
- Platform: remove `Websites.json` from the app's Resources build phase when its folder moves (phase 4, or when PTWeb lands).

## Tests

| Test file | Where |
| --- | --- |
| `ScheduleTests`, `NowNextTests` | `Packages/PTAgenda/Tests` (to confirm they use no TeamFeatures / app host) |
| `WebsitesJSONTests` | `Packages/PTWeb/Tests` |
| `AppReviewTests` (CheckoutPolicy parts) | split: CheckoutPolicy cases to PTWeb, the rest stays |
| `TeamFeaturesTests` | stays in the Team app (it builds `TeamFeatures`) |

## Conductor's answers (8 Nov, 00:48 Lisbon)

1. JSON-level bridge. `ScheduleDocument`, `ScheduleMerge`, `ScheduleID` stay in PTAgenda.
2. Platform moves `Event` and `FeedClient.site` into PTCore in phase 2.
3. `ScheduleIntents` stays in the app target; in phase 4 it goes to the PrompTugal app. The Team app gets no intents tonight.
4. The public app gets a no-team `AgendaTeamSource` that returns nothing.

5. `News/AgendaView.swift` (204 lines) moves into PTAgenda. It reads `ScheduleStore` from the environment and uses `.webTwin` and `FeedClient` (PTCore / PTWeb). Design reviews it.
6. `App/ReaderStore.swift` (168 lines) moves into PTAgenda. Its `TeamFeatures.shared.schedule` and `TeamFeatures.shared.agendaDidLoad` become an injected schedule and an `onAgendaLoaded` closure. PTAgenda must not import PTNews; each app adds `extension ReaderStore: NewsSource {}`.
7. Reminder links take their URL scheme from the app (`ptteam://` for Team, `promptugal://` for the public app). `ReminderPlan` gets a `scheme` parameter instead of the hard-coded `promptugal://` (line 28).

Waiting for phase 2 (PTCore on dev).

## Questions for the conductor (answered above)

1. **Schedule types across PTWeb and PTAgenda.** The bridge reads and merges `ScheduleDocument`. I plan a JSON-level protocol so PTWeb does not know the type. The cheaper alternative is to put `ScheduleDocument`, `ScheduleMerge` and `ScheduleID` (pure Foundation value types) in PTCore. Which do you want? Default: JSON-level protocol, no PTCore change.
2. **`Event` and `FeedClient.site`** are in `App/Content.swift`. PTNews, PTAgenda and PTAsk all need `Event`. Is platform moving the `Event` struct (and `FeedClient.site`) to PTCore in phase 2? If not, I need it there.
3. **App Intents in a package.** `ScheduleIntents.swift` (SessionEntity, SessionQuery, intents) is an App Intents type. Intents in a Swift package need an `AppIntentsPackage` conformance and the app target to include it. Safer: keep `ScheduleIntents.swift` in the app target and move only the stores. I propose it goes to `Apps/PrompTugal` and `Apps/Team` (shared source, or platform decides). OK?

Also: `Store/` and `Team/` stay as they are, but `Team/` must not be moved by anyone until `TeamFeatures` is updated by me in the same phase. I will edit `Team/TeamFeatures.swift`, `Team/TeamStore.swift` conformances and `Store/` only for `public`/conformance lines.

## Needs from others

- design or platform: `Shell/AppShell.swift:97` accepts only `url.scheme == "promptugal"`. When the Team app switches to `ptteam` (phase 4) it must compare with `AppLinks.scheme`, or reminder taps and deep links stop opening.

- platform: `Event`, `FeedClient.site`, `Lisbon`, `HTTPClient` public in PTCore (question 2). PTWeb and PTAgenda `Package.swift` with `resources:` for PTWeb.
- design: review `WebPage.swift` and `WebsiteView.swift` after the move (only `public`, `import`, and `WebBridgeHub` changes).
