-
Notifications
You must be signed in to change notification settings - Fork 0
fix: restore Prairie features dropped by upstream syncs and add an invariant guard #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| name: Prairie invariants | ||
|
|
||
| # Build-free tripwire for Prairie-only code that upstream Silo syncs have | ||
| # silently deleted or unwired before (Live TV entries, Quick Connect, the | ||
| # connect list, trickplay, image-format header). Unfiltered so it runs on | ||
| # every PR, including a sync that only touches files it resolves to upstream. | ||
| # See docs/upstream-sync.md. | ||
|
|
||
| on: | ||
| pull_request: | ||
| push: | ||
| branches: [main] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| prairie-invariants: | ||
| name: Prairie invariants | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Check Prairie invariants | ||
| run: scripts/check-prairie-invariants.sh |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Syncing upstream Silo into Prairie Apple | ||
|
|
||
| Prairie Apple is an AGPL fork of `Silo-Server/silo-apple`. Upstream syncs are | ||
| routine, and they are also the main way Prairie loses work. A conflicted file | ||
| resolved wholesale to upstream deletes Prairie's hunks, and the code often | ||
| still compiles because the Prairie type survives while nothing calls it. | ||
| Past losses: the first-run connect list (`ConnectServerListView`), the iOS | ||
| Quick Connect and update-status rows, the `X-Prairie-Image-Formats` header on | ||
| API requests, iOS trickplay scrub tiles, and Silo's diagnostics host coming | ||
| back. | ||
|
|
||
| ## Rules | ||
|
|
||
| - Sync on a `sync/upstream-<date>` branch with a real `git merge upstream/main` | ||
| and land it with **"Create a merge commit"**. Never squash; it drops upstream | ||
| ancestry and the next sync re-conflicts everything. | ||
| - **A sync PR merges only with CI green**, including the macOS `Apple | ||
| regression` and `Unit Tests` jobs. A failing test after a sync usually means | ||
| Prairie code was dropped while its test was kept. | ||
| - `Prairie invariants` (`scripts/check-prairie-invariants.sh`) must pass. If it | ||
| fails, restore the code. Do not edit the manifest to make it pass. | ||
|
|
||
| ## After resolving conflicts | ||
|
|
||
| 1. Rebrand fallout: `SiloAPI` → `PrairieAPI`, `silo*` colors and theme names → | ||
| `prairie*`, `X-Silo-*` → `X-Prairie-*`, `siloserver.org` hosts → | ||
| `prairieserver.org`. Keep wire tokens the server and other clients share | ||
| (`x-silo-original`, `_silocast._tcp`, `_silopair._tcp`, | ||
| `silo-public-diagnostics-v1`) unless the server changes them. | ||
| 2. Hunk audit: for each Prairie PR, check its added lines still exist in the | ||
| merge result (`gh pr diff <n>`). Upstream moves files, so search the whole | ||
| tree, not only the original path. | ||
| 3. Reachability: every Prairie screen must still have a caller. Grep for | ||
| `LiveTVChannelListView(`, `QuickConnectView()`, `ConnectServerListView(`. | ||
| A view type with no call site means lost wiring. | ||
| 4. New upstream tests that assert Silo strings or hosts need the Prairie value. | ||
| 5. Run `scripts/check-prairie-invariants.sh` and `scripts/ci/check-no-api-v1.sh` | ||
| locally; both run on Linux. | ||
|
|
||
| ## When you restore something a sync dropped | ||
|
|
||
| Add an anchor for it to `scripts/prairie-invariants.txt` in the same PR: the | ||
| file, the minimum number of matches, a regex on a stable identifier, and where | ||
| it came from. That is what stops the next sync from deleting it again. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -473,14 +473,15 @@ struct MobilePlayerControls: View { | |
| ) | ||
| .overlay(alignment: .topLeading) { | ||
| if viewModel.isScrubbing { | ||
| let previewInset: CGFloat = viewModel.scrubPreviewImage == nil ? 80 : 102 | ||
| let hasArtwork = hasScrubPreviewArtwork | ||
| let previewInset: CGFloat = hasArtwork ? 102 : 80 | ||
| scrubPreviewBubble | ||
| .position( | ||
| x: min( | ||
| max(width * progress, previewInset), | ||
| max(width - previewInset, previewInset) | ||
| ), | ||
| y: viewModel.scrubPreviewImage == nil ? -36 : -92 | ||
| y: hasArtwork ? -92 : -36 | ||
| ) | ||
| .transition(.opacity) | ||
| .allowsHitTesting(false) | ||
|
|
@@ -513,14 +514,29 @@ struct MobilePlayerControls: View { | |
| /// Floating time + chapter readout pinned above the touch point while | ||
| /// scrubbing. Presentation-only: reads the same `scrubPreviewTime` the | ||
| /// seek machinery already maintains. | ||
| /// | ||
| /// Prairie: the server's trickplay sprite tile wins (PR #28, same as the | ||
| /// tvOS scrubber), then Aether's decoded frame, then the chapter still. | ||
| private var scrubPreviewBubble: some View { | ||
| VStack(spacing: 6) { | ||
| if let image = viewModel.scrubPreviewImage { | ||
| if let tile = scrubTrickplayTile { | ||
| TrickplayTileImage(tile: tile, displayWidth: 176) | ||
|
Comment on lines
+522
to
+523
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '465,580p' iosApp/iosApp/Screens/Player/iOS/MobilePlayerControls.swift
sed -n '1,180p' iosApp/iosApp/Screens/Player/TrickplayPreviewView.swift
rg -n 'struct CachedAsyncImage|struct TrickplayTileImage|scrubPreviewImage' iosApp/iosAppRepository: Prairie-Server/prairie-apple Length of output: 10297 Keep fallback artwork visible when the trickplay image is unavailable.
Show the fallback artwork until the trickplay image loads successfully, and keep it available if loading fails. Preserve the trickplay tile as the preferred artwork after it loads. 🤖 Prompt for AI Agents |
||
| .clipShape(RoundedRectangle(cornerRadius: 9, style: .continuous)) | ||
| } else if let image = viewModel.scrubPreviewImage { | ||
| Image(decorative: image, scale: 1) | ||
| .resizable() | ||
| .aspectRatio(contentMode: .fill) | ||
| .frame(width: 176, height: 99) | ||
| .clipShape(RoundedRectangle(cornerRadius: 9, style: .continuous)) | ||
| } else if let chapterURL = scrubChapterThumbnailURL { | ||
| CachedAsyncImage( | ||
| url: chapterURL, | ||
| targetSize: CGSize(width: 176, height: 99), | ||
| contentMode: .fill, | ||
| placeholderStyle: .clear | ||
| ) | ||
| .frame(width: 176, height: 99) | ||
| .clipShape(RoundedRectangle(cornerRadius: 9, style: .continuous)) | ||
| } | ||
| Text(PlayerTimeFormatter.formatHMS(viewModel.scrubPreviewTime)) | ||
| .font(.system(size: 19, weight: .bold)) | ||
|
|
@@ -538,6 +554,22 @@ struct MobilePlayerControls: View { | |
| .fixedSize() | ||
| } | ||
|
|
||
| private var scrubTrickplayTile: TrickplayTilePreview? { | ||
| Trickplay.resolveTile(viewModel.trickplay, seconds: viewModel.scrubPreviewTime) | ||
| } | ||
|
|
||
| private var scrubChapterThumbnailURL: String? { | ||
| guard let url = viewModel.chapterThumbnailURL(at: viewModel.scrubPreviewTime), | ||
| !url.isEmpty else { return nil } | ||
| return url | ||
| } | ||
|
|
||
| private var hasScrubPreviewArtwork: Bool { | ||
| scrubTrickplayTile != nil | ||
| || viewModel.scrubPreviewImage != nil | ||
| || scrubChapterThumbnailURL != nil | ||
| } | ||
|
|
||
| private func chapterTitle(at time: Double) -> String? { | ||
| guard let chapter = viewModel.chapters.last(where: { $0.time <= time }) else { return nil } | ||
| return chapter.title ?? "Chapter \(chapter.index + 1)" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
✅ Runtime observed
🏁 Script executed:
Repository: Prairie-Server/prairie-apple
Length of output: 166
🏁 Script executed:
Repository: Prairie-Server/prairie-apple
Length of output: 4161
🏁 Script executed:
Repository: Prairie-Server/prairie-apple
Length of output: 21850
Verify the collector is available before release.
The default URL does not resolve. Confirm that
diagnostics.prairieserver.orgresolves and that its public capabilities endpoint returns the expected collector identity before release.🤖 Prompt for AI Agents