diff --git a/.DS_Store b/.DS_Store index bd5957a..a2b672a 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/AGENTS.md b/AGENTS.md index 633878c..cd1e980 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,83 +97,43 @@ bd close # Complete work ## Required Turn Documentation -At the end of every completed implementation task, before final handoff, create a user-readable HTML document describing the work. +At the end of repository work, use this decision flow: -This documentation is mandatory whenever code, configuration, tests, or project files were changed. +1. **Classify the task.** + - If the change is minor/trivial under the exemption list below, do not create a turn document. + - If the task changed code, configuration, tests, project files, or substantive docs inside the repo, create or update a turn document. + - If classification is ambiguous or mixed, ask the user before creating a turn document. +2. **Document substantive implementation work.** + - New work: create `docs/turns/YYYY-MM-DD-short-task-name.html`. + - Minor update to a previous substantive change: update that existing turn document instead of creating a duplicate. +3. **Complete the closeout for documented work.** + - Update Beads. + - Run relevant quality gates, or document any failures. + - Commit changes. + - Run `bd dolt push`. + - Run `git push`. + - Confirm `git status` shows the branch is up to date with `forgejo/`. -### Precedence and classification +The minor/trivial exemptions override the general turn-document rule. -Use this decision order before creating a turn document: +### Minor/Trivial Exemptions -1. Check the minor/trivial exemption checklist below first. -2. If the task clearly matches an exemption, do not create a turn document. -3. If the task is a clearly substantive implementation change, create a turn document. -4. If classification is ambiguous or mixed, ask the user before creating a turn document. - -The minor/trivial exemptions override the general mandatory turn-document rule. - -For diff content in turn documentation (including "Code diffs" and "Relevant Diff Snippets"), use `@pierre/diffs` output by default. Do not run `npx @pierre/diffs`; the package is a rendering library and does not expose a CLI executable. Generate rendered diff HTML with `@pierre/diffs/ssr`, usually `preloadPatchDiff`, and insert that rendered output into the turn document. `preloadPatchDiff` expects exactly one file diff per call, so split multi-file diffs into one patch per file and concatenate the rendered HTML. If `@pierre/diffs/ssr` is unavailable because of a real tool or blocking error, use a clearly labeled plain diff/code block fallback and note why. - -### No turn document for minor/trivial checklist matches - -Do not create a turn document when the change is minor/trivial and cleanly matches one of these categories: +Do not create a turn document when the change cleanly matches one of these categories: - `AGENTS.md` changes or other documentation-only changes - Syntax-only fixes - Refactor-only changes with no behavior change - PR/conflict reconciliation work - Issue-tracker-only updates such as `beads/issues.json` -- Support-file changes that only accompany one of the exempt categories above (for example lockfile or manifest updates required for docs-workflow changes) +- Support-file changes that only accompany one of the exempt categories above, such as lockfile or manifest updates required for docs-workflow changes If a change does not cleanly fit either exempt or substantive buckets, ask the user before creating a turn document. -### When making a minor update to a previous change, update the existing documentation instead of creating a new file. Use the following format: +### Turn Document Requirements -**"New Changes as of {time and date at which the change was made}"** -- **Summary of changes** -- **Why this change was made** -- **Code diffs** (use rendered `@pierre/diffs/ssr` output by default; do not use `npx @pierre/diffs`; if unavailable, include a clearly labeled plain diff/code block and note why) -- **Related issues or PRs** +Use the `impeccable` skill to structure and style the document as clean, readable HTML. For this repository, `impeccable` is the styling and layout authority for turn documents when available. Do not apply global non-repo computer-task house styling to repository turn documents. -Additionally, add a note to each section explaining why the changes were made. - -### Location - -Save the document in: - -```text -docs/turns/ -``` - -Use a clear timestamped filename: - -```text -docs/turns/YYYY-MM-DD-short-task-name.html -``` - -Example: - -```text -docs/turns/2026-05-14-add-market-replay-controls.html -``` - -### Format - -Use the `impeccable` skill to structure and style the document as clean, readable HTML. - -For this repository, `impeccable` is the styling and layout authority for turn documents when available. Do not apply global non-repo computer-task house styling to repository turn documents. - -If the `impeccable` skill is unavailable or blocked by an actual tool/file error, still create a well-structured standalone HTML file with: - -- A concise summary at the top -- A detailed explanation of what changed -- Relevant context or background -- Specific code snippets or examples when helpful -- Issues, limitations, tradeoffs, or mitigations -- Validation performed, including tests, builds, linters, or manual checks -- Any remaining follow-up work, with corresponding Beads issue IDs when applicable - -### Required Sections +If `impeccable` is unavailable or blocked by an actual tool/file error, still create a well-structured standalone HTML file. Each turn document must include these sections: @@ -181,32 +141,59 @@ Each turn document must include these sections: 2. **Changes Made** 3. **Context** 4. **Important Implementation Details** -5. **Relevant Diff Snippets** (render with `@pierre/diffs/ssr` output by default; do not use `npx @pierre/diffs`; if unavailable, include a clearly labeled plain diff/code block and note why) +5. **Relevant Diff Snippets** (follow the **Rendered Diff Documentation** rule) 6. **Expected Impact for End-Users** 7. **Validation** 8. **Issues, Limitations, and Mitigations** 9. **Follow-up Work** -### Completion Rule +For a minor update to a previous substantive change, add this section to the existing document: -A task that requires a turn document is not complete until: +**"New Changes as of {time and date at which the change was made}"** +- **Summary of changes** +- **Why this change was made** +- **Code diffs** (follow the **Rendered Diff Documentation** rule) +- **Related issues or PRs** -1. The Beads workflow is updated -2. The turn document is created in `docs/turns` -3. Relevant quality gates have passed or failures are documented -4. Changes are committed -5. `bd dolt push` succeeds -6. `git push` succeeds -7. `git status` shows the branch is up to date with `forgejo/` +### Rendered Diff Documentation -For tasks that do require turn documentation, the document may be brief when scope is small, but it must clearly explain what changed and how it was validated. +When turn documentation needs rendered code diffs, use `@pierre/diffs` through its ESM server-side renderer. + +Use `@pierre/diffs/ssr` with Node ESM imports. Do not test, load, or diagnose this package with CommonJS `require()`, because `@pierre/diffs` is ESM and `require('@pierre/diffs/ssr')` can falsely look like an export or package failure. + +Preferred availability check: + +```bash +node --input-type=module -e "import { preloadPatchDiff } from '@pierre/diffs/ssr'; console.log(typeof preloadPatchDiff)" +``` + +Preferred rendering pattern: + +```bash +node --input-type=module <<'NODE' +import { readFileSync, writeFileSync } from 'node:fs'; +import { preloadPatchDiff } from '@pierre/diffs/ssr'; + +const patch = readFileSync('/tmp/change.patch', 'utf8'); +const { prerenderedHTML } = await preloadPatchDiff({ + patch, + options: { diffType: 'unified' } +}); + +writeFileSync('/tmp/rendered-diff.html', prerenderedHTML); +NODE +``` + +`preloadPatchDiff` expects exactly one file diff per call. If a git diff contains multiple files, split it into one patch per file, render each file patch separately, and concatenate the rendered HTML into the turn document. + +Do not run `npx @pierre/diffs`; the package is a rendering library and does not expose a CLI executable. + +Only use a clearly labeled plain diff or code-block fallback when the ESM import-and-render pattern above fails because of a real tool, install, or runtime error. Document the failure briefly in the turn document. ## Plan Mode Documentation When working in plan mode, do not modify implementation files. -At the end of plan mode, provide a concise summary of the plan and ask the user whether they want to proceed with implementation. - If the user asks to save the plan, create a user-readable HTML plan document in: ```text @@ -228,10 +215,3 @@ The plan document should be labeled clearly as a plan and should include: 5. **Implementation Steps** 6. **Risks, Limitations, and Mitigations** 7. **Open Questions** - -Always do the following when you finish a task, finish the beads workflow and and make a commit: -- Document the changes in a user-readable format -- Use the impeccable skill to structure the document as HTML -- Create a clear, concise summary of the changes at the top, followed by a detailed description of the changes, including any relevant context or background as well as specific code snippets or examples. -- Note any relevant issues or limitations that were addressed or mitigated by the changes. -- The HTML file should be stored in the `docs/turns` directory. It should include the current date and time, as well as a brief explanation of changes. e.g. docs/turns/YYYY-MM-DD-{description}.html diff --git a/Dreamio.xcodeproj/project.xcworkspace/xcuserdata/kell.xcuserdatad/UserInterfaceState.xcuserstate b/Dreamio.xcodeproj/project.xcworkspace/xcuserdata/kell.xcuserdatad/UserInterfaceState.xcuserstate index 8e4627e..a775c5b 100644 Binary files a/Dreamio.xcodeproj/project.xcworkspace/xcuserdata/kell.xcuserdatad/UserInterfaceState.xcuserstate and b/Dreamio.xcodeproj/project.xcworkspace/xcuserdata/kell.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Dreamio.xcodeproj/xcuserdata/kell.xcuserdatad/xcschemes/xcschememanagement.plist b/Dreamio.xcodeproj/xcuserdata/kell.xcuserdatad/xcschemes/xcschememanagement.plist index 8fbf89e..71b86a8 100644 --- a/Dreamio.xcodeproj/xcuserdata/kell.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Dreamio.xcodeproj/xcuserdata/kell.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Dreamio.xcscheme_^#shared#^_ orderHint - 0 + 2 diff --git a/Dreamio.xcworkspace/xcuserdata/kell.xcuserdatad/UserInterfaceState.xcuserstate b/Dreamio.xcworkspace/xcuserdata/kell.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..d6bf1d4 Binary files /dev/null and b/Dreamio.xcworkspace/xcuserdata/kell.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/docs/turns/2026-05-25-fix-native-player-controls-tap.html b/docs/turns/2026-05-25-fix-native-player-controls-tap.html new file mode 100644 index 0000000..7436753 --- /dev/null +++ b/docs/turns/2026-05-25-fix-native-player-controls-tap.html @@ -0,0 +1,265 @@ + + + + + + Fix Native Player Controls Tap-to-Show + + + +
+
+
Turn document ยท 2026-05-25 05:28 EDT
+

Fix Native Player Controls Tap-to-Show

+

Native player controls can now be brought back after they auto-hide or are hidden by tapping the player. The fix gives player taps a reliable full-screen gesture surface above the VLC video view while keeping visible controls interactive.

+
+ Issue: dreamio-wgk + File: Dreamio/NativePlayerViewController.swift + Validation: Xcode build passed +
+
+ +
+

Summary

+

Fixed the native playback overlay so hidden controls are not effectively gone forever. A transparent tap surface now receives taps over the video, and hidden control views stop intercepting touches until they are visible again.

+
+ +
+

Changes Made

+
    +
  • Added a full-screen transparent tapSurfaceView above the VLC drawable and below the loading, failure, controls, and close-button layers.
  • +
  • Moved the tap gesture recognizer from the root view to that tap surface so player taps are handled consistently.
  • +
  • Disabled user interaction on the controls container and close button while they are hidden, then re-enabled it when controls are revealed.
  • +
+
+ +
+

Context

+

The native player uses MobileVLCKit for video rendering and an overlay built in UIKit for playback controls. Before this change, the gesture recognizer was attached to the root view. Once controls faded out, the visible controls had alpha zero but still occupied their layout area, and the video drawable could also interfere with root-level tap handling. That left some taps with no route back to revealControls().

+
+ +
+

Important Implementation Details

+

The tap surface is inserted immediately after backend.view, which keeps it above the video but below the actual controls. This preserves normal button and slider behavior when controls are visible while making the rest of the player a reliable tap target.

+

When hideControls() runs, the controls and close button are also made non-interactive. This matters because alpha-zero UIKit views can still participate in hit testing unless interaction is disabled or the views are hidden.

+
+ +
+

Relevant Diff Snippets

+

The diff below is rendered with @pierre/diffs/ssr using preloadPatchDiff.

+
+
Dreamio/NativePlayerViewController.swift
-1+18
35 unmodified lines
36
37
38
39
40
41
125 unmodified lines
167
168
169
170
171
172
9 unmodified lines
182
183
184
185
186
187
188
19 unmodified lines
208
209
210
211
212
213
124 unmodified lines
338
339
340
341
342
343
2 unmodified lines
346
347
348
349
350
351
35 unmodified lines
return view
}()
+
private let playPauseButton = NativePlayerViewController.iconButton(systemName: "pause.fill", label: "Play or Pause")
private let jumpBackButton = NativePlayerViewController.iconButton(systemName: "gobackward.15", label: "Jump Back 15 Seconds")
private let jumpForwardButton = NativePlayerViewController.iconButton(systemName: "goforward.15", label: "Jump Forward 15 Seconds")
125 unmodified lines
+
private func configureLayout() {
view.addSubview(backend.view)
view.addSubview(loadingView)
view.addSubview(failureLabel)
view.addSubview(controlsContainer)
9 unmodified lines
+
let tap = UITapGestureRecognizer(target: self, action: #selector(toggleControlsVisibility))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
+
let controlRow = UIStackView(arrangedSubviews: [jumpBackButton, playPauseButton, jumpForwardButton, captionsButton])
controlRow.translatesAutoresizingMaskIntoConstraints = false
19 unmodified lines
backend.view.topAnchor.constraint(equalTo: view.topAnchor),
backend.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
loadingView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
loadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
+
124 unmodified lines
}
+
private func revealControls() {
UIView.animate(withDuration: 0.18) {
self.controlsContainer.alpha = 1
self.closeButton.alpha = 1
2 unmodified lines
}
+
private func hideControls() {
UIView.animate(withDuration: 0.24) {
self.controlsContainer.alpha = 0
self.closeButton.alpha = 0
35 unmodified lines
36
37
38
39
40
41
42
43
44
45
46
47
48
125 unmodified lines
174
175
176
177
178
179
180
9 unmodified lines
190
191
192
193
194
195
196
19 unmodified lines
216
217
218
219
220
221
222
223
224
225
226
124 unmodified lines
351
352
353
354
355
356
357
358
2 unmodified lines
361
362
363
364
365
366
367
368
35 unmodified lines
return view
}()
+
private let tapSurfaceView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
return view
}()
+
private let playPauseButton = NativePlayerViewController.iconButton(systemName: "pause.fill", label: "Play or Pause")
private let jumpBackButton = NativePlayerViewController.iconButton(systemName: "gobackward.15", label: "Jump Back 15 Seconds")
private let jumpForwardButton = NativePlayerViewController.iconButton(systemName: "goforward.15", label: "Jump Forward 15 Seconds")
125 unmodified lines
+
private func configureLayout() {
view.addSubview(backend.view)
view.addSubview(tapSurfaceView)
view.addSubview(loadingView)
view.addSubview(failureLabel)
view.addSubview(controlsContainer)
9 unmodified lines
+
let tap = UITapGestureRecognizer(target: self, action: #selector(toggleControlsVisibility))
tap.cancelsTouchesInView = false
tapSurfaceView.addGestureRecognizer(tap)
+
let controlRow = UIStackView(arrangedSubviews: [jumpBackButton, playPauseButton, jumpForwardButton, captionsButton])
controlRow.translatesAutoresizingMaskIntoConstraints = false
19 unmodified lines
backend.view.topAnchor.constraint(equalTo: view.topAnchor),
backend.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
tapSurfaceView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tapSurfaceView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tapSurfaceView.topAnchor.constraint(equalTo: view.topAnchor),
tapSurfaceView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
loadingView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
loadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
+
124 unmodified lines
}
+
private func revealControls() {
controlsContainer.isUserInteractionEnabled = true
closeButton.isUserInteractionEnabled = true
UIView.animate(withDuration: 0.18) {
self.controlsContainer.alpha = 1
self.closeButton.alpha = 1
2 unmodified lines
}
+
private func hideControls() {
controlsContainer.isUserInteractionEnabled = false
closeButton.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.24) {
self.controlsContainer.alpha = 0
self.closeButton.alpha = 0
+
+
+ +
+

Expected Impact for End-Users

+

Users should be able to tap the native player to hide controls and tap the video again to bring them back. Auto-hidden controls should behave the same way, so playback is no longer trapped in a controls-hidden state.

+
+ +
+

Validation

+
+

Passed: xcodebuild build -workspace Dreamio.xcworkspace -scheme Dreamio -destination 'generic/platform=iOS'

+
+

The build succeeded for the Dreamio scheme against a generic iOS destination. Manual on-device interaction was not run in this turn, so the remaining risk is limited to real touch behavior across physical device sizes.

+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • The fix is intentionally scoped to tap routing and hidden overlay hit testing. It does not change VLC playback state, seeking, captions, or close behavior.
  • +
  • Manual device testing is still useful because UIKit gesture delivery around embedded native video surfaces can vary with presentation details.
  • +
  • The Xcode build reports an existing warning that the MobileVLCKit preparation script has no declared outputs. This was not introduced by the tap fix.
  • +
+
+ +
+

Follow-up Work

+
    +
  • No new follow-up issue is required for this fix.
  • +
  • Optional future improvement: add an injectable player overlay test harness so tap-to-show behavior can be exercised without launching MobileVLCKit on a device.
  • +
+
+
+ + \ No newline at end of file