From e8993ee7d11ffb9fd660ec83ff35324033b57bb5 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Sun, 24 May 2026 04:33:39 -0400 Subject: [PATCH 1/2] bd init: initialize beads issue tracking --- .beads/.gitignore | 70 ++++++++++++++++++++++++ .beads/README.md | 81 ++++++++++++++++++++++++++++ .beads/config.yaml | 55 +++++++++++++++++++ .beads/hooks/post-checkout | 24 +++++++++ .beads/hooks/post-merge | 24 +++++++++ .beads/hooks/pre-commit | 24 +++++++++ .beads/hooks/pre-push | 24 +++++++++ .beads/hooks/prepare-commit-msg | 24 +++++++++ .beads/interactions.jsonl | 0 .beads/metadata.json | 7 +++ .claude/settings.json | 26 +++++++++ .gitignore | 5 ++ AGENTS.md | 96 +++++++++++++++++++++++++++++++++ CLAUDE.md | 70 ++++++++++++++++++++++++ 14 files changed, 530 insertions(+) create mode 100644 .beads/.gitignore create mode 100644 .beads/README.md create mode 100644 .beads/config.yaml create mode 100755 .beads/hooks/post-checkout create mode 100755 .beads/hooks/post-merge create mode 100755 .beads/hooks/pre-commit create mode 100755 .beads/hooks/pre-push create mode 100755 .beads/hooks/prepare-commit-msg create mode 100644 .beads/interactions.jsonl create mode 100644 .beads/metadata.json create mode 100644 .claude/settings.json create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/.beads/.gitignore b/.beads/.gitignore new file mode 100644 index 0000000..304f708 --- /dev/null +++ b/.beads/.gitignore @@ -0,0 +1,70 @@ +# Dolt database (managed by Dolt, not git) +dolt/ +embeddeddolt/ + +# Runtime files +bd.sock +bd.sock.startlock +sync-state.json +last-touched +.exclusive-lock + +# Daemon runtime (lock, log, pid) +daemon.* + +# Push state (runtime, per-machine) +push-state.json + +# Lock files (various runtime locks) +*.lock + +# Credential key (encryption key for federation peer auth — never commit) +.beads-credential-key + +# Local version tracking (prevents upgrade notification spam after git ops) +.local_version + +# Worktree redirect file (contains relative path to main repo's .beads/) +# Must not be committed as paths would be wrong in other clones +redirect + +# Sync state (local-only, per-machine) +# These files are machine-specific and should not be shared across clones +.sync.lock +export-state/ +export-state.json + +# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned) +ephemeral.sqlite3 +ephemeral.sqlite3-journal +ephemeral.sqlite3-wal +ephemeral.sqlite3-shm + +# Dolt server management (auto-started by bd) +dolt-server.pid +dolt-server.log +dolt-server.lock +dolt-server.port +dolt-server.activity + +# Corrupt backup directories (created by bd doctor --fix recovery) +*.corrupt.backup/ + +# Backup data (auto-exported JSONL, local-only) +backup/ + +# Per-project environment file (Dolt connection config, GH#2520) +.env + +# Legacy files (from pre-Dolt versions) +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm +db.sqlite +bd.db +# NOTE: Do NOT add negation patterns here. +# They would override fork protection in .git/info/exclude. +# Config files (metadata.json, config.yaml) are tracked by git by default +# since no pattern above ignores them. diff --git a/.beads/README.md b/.beads/README.md new file mode 100644 index 0000000..dbfe363 --- /dev/null +++ b/.beads/README.md @@ -0,0 +1,81 @@ +# Beads - AI-Native Issue Tracking + +Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. + +## What is Beads? + +Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. + +**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) + +## Quick Start + +### Essential Commands + +```bash +# Create new issues +bd create "Add user authentication" + +# View all issues +bd list + +# View issue details +bd show + +# Update issue status +bd update --claim +bd update --status done + +# Sync with Dolt remote +bd dolt push +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in Dolt database with version control and branching +- **AI-friendly**: CLI-first design works perfectly with AI coding agents +- **Branch-aware**: Issues can follow your branch workflow +- **Always in sync**: Auto-syncs with your commits + +## Why Beads? + +✨ **AI-Native Design** +- Built specifically for AI-assisted development workflows +- CLI-first interface works seamlessly with AI coding agents +- No context switching to web UIs + +🚀 **Developer Focused** +- Issues live in your repo, right next to your code +- Works offline, syncs when you push +- Fast, lightweight, and stays out of your way + +🔧 **Git Integration** +- Automatic sync with git commits +- Branch-aware issue tracking +- Dolt-native three-way merge resolution + +## Get Started with Beads + +Try Beads in your own projects: + +```bash +# Install Beads +curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize in your repo +bd init + +# Create your first issue +bd create "Try out Beads" +``` + +## Learn More + +- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) +- **Quick Start Guide**: Run `bd quickstart` +- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) + +--- + +*Beads: Issue tracking that moves at the speed of thought* ⚡ diff --git a/.beads/config.yaml b/.beads/config.yaml new file mode 100644 index 0000000..07342c6 --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,55 @@ +# Beads Configuration File +# This file configures default behavior for all bd commands in this repository +# All settings can also be set via environment variables (BD_* prefix) +# or overridden with command-line flags + +# Issue prefix for this repository (used by bd init) +# If not set, bd init will auto-detect from directory name +# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. +# issue-prefix: "" + +# Use no-db mode: JSONL-only, no Dolt database +# When true, bd will use .beads/issues.jsonl as the source of truth +# no-db: false + +# Enable JSON output by default +# json: false + +# Feedback title formatting for mutating commands (create/update/close/dep/edit) +# 0 = hide titles, N > 0 = truncate to N characters +# output: +# title-length: 255 + +# Default actor for audit trails (overridden by BEADS_ACTOR or --actor) +# actor: "" + +# Export events (audit trail) to .beads/events.jsonl on each flush/sync +# When enabled, new events are appended incrementally using a high-water mark. +# Use 'bd export --events' to trigger manually regardless of this setting. +# events-export: false + +# Multi-repo configuration (experimental - bd-307) +# Allows hydrating from multiple repositories and routing writes to the correct database +# repos: +# primary: "." # Primary repo (where this database lives) +# additional: # Additional repos to hydrate from (read-only) +# - ~/beads-planning # Personal planning repo +# - ~/work-planning # Work planning repo + +# JSONL backup (periodic export for off-machine recovery) +# Auto-enabled when a git remote exists. Override explicitly: +# backup: +# enabled: false # Disable auto-backup entirely +# interval: 15m # Minimum time between auto-exports +# git-push: false # Disable git push (export locally only) +# git-repo: "" # Separate git repo for backups (default: project repo) + +# Integration settings (access with 'bd config get/set') +# Non-secret keys (stored in the database): +# - jira.url, jira.project +# - linear.team_id +# - github.org, github.repo +# +# Secret keys (stored in this file but prefer env vars to avoid git exposure): +# - linear.api_key → use LINEAR_API_KEY env var instead +# - github.token → use GITHUB_TOKEN env var instead diff --git a/.beads/hooks/post-checkout b/.beads/hooks/post-checkout new file mode 100755 index 0000000..7d35c68 --- /dev/null +++ b/.beads/hooks/post-checkout @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.4 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run post-checkout "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'post-checkout' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run post-checkout "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'post-checkout'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.4 --- diff --git a/.beads/hooks/post-merge b/.beads/hooks/post-merge new file mode 100755 index 0000000..1f458ba --- /dev/null +++ b/.beads/hooks/post-merge @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.4 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run post-merge "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'post-merge' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run post-merge "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'post-merge'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.4 --- diff --git a/.beads/hooks/pre-commit b/.beads/hooks/pre-commit new file mode 100755 index 0000000..ad1fb16 --- /dev/null +++ b/.beads/hooks/pre-commit @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.4 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run pre-commit "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'pre-commit' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run pre-commit "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'pre-commit'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.4 --- diff --git a/.beads/hooks/pre-push b/.beads/hooks/pre-push new file mode 100755 index 0000000..35c2a69 --- /dev/null +++ b/.beads/hooks/pre-push @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.4 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run pre-push "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'pre-push' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run pre-push "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'pre-push'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.4 --- diff --git a/.beads/hooks/prepare-commit-msg b/.beads/hooks/prepare-commit-msg new file mode 100755 index 0000000..a72277d --- /dev/null +++ b/.beads/hooks/prepare-commit-msg @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.4 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run prepare-commit-msg "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'prepare-commit-msg' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run prepare-commit-msg "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'prepare-commit-msg'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.4 --- diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 0000000..366a4c4 --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,7 @@ +{ + "database": "dolt", + "backend": "dolt", + "dolt_mode": "embedded", + "dolt_database": "dreamio", + "project_id": "f6ce7af2-f1c8-4f6c-b8ef-88e924f15d93" +} \ No newline at end of file diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..963a538 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,26 @@ +{ + "hooks": { + "PreCompact": [ + { + "hooks": [ + { + "command": "bd prime", + "type": "command" + } + ], + "matcher": "" + } + ], + "SessionStart": [ + { + "hooks": [ + { + "command": "bd prime", + "type": "command" + } + ], + "matcher": "" + } + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a04c86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ + +# Beads / Dolt files (added by bd init) +.dolt/ +*.db +.beads-credential-key diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bc2ae10 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,96 @@ +# Agent Instructions + +This project uses **bd** (beads) for issue tracking. Run `bd prime` for full workflow context. + +> **Architecture in one line:** Issues live in a local Dolt database +> (`.beads/dolt/`); cross-machine sync uses `bd dolt push/pull` (a +> git-compatible protocol), stored under `refs/dolt/data` on your git +> remote — separate from `refs/heads/*` where your code lives. +> `.beads/issues.jsonl` is a passive export, not the wire protocol. +> +> See [SYNC_CONCEPTS.md](https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md) +> for the one-screen overview and anti-patterns (don't treat JSONL as the +> source of truth; don't `bd import` during normal operation; don't +> reach for third-party Dolt hosting before trying the default). + +## Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work atomically +bd close # Complete work +bd dolt push # Push beads data to remote +``` + +## Non-Interactive Shell Commands + +**ALWAYS use non-interactive flags** with file operations to avoid hanging on confirmation prompts. + +Shell commands like `cp`, `mv`, and `rm` may be aliased to include `-i` (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input. + +**Use these forms instead:** +```bash +# Force overwrite without prompting +cp -f source dest # NOT: cp source dest +mv -f source dest # NOT: mv source dest +rm -f file # NOT: rm file + +# For recursive operations +rm -rf directory # NOT: rm -r directory +cp -rf source dest # NOT: cp -r source dest +``` + +**Other commands that may prompt:** +- `scp` - use `-o BatchMode=yes` for non-interactive +- `ssh` - use `-o BatchMode=yes` to fail instead of prompting +- `apt-get` - use `-y` flag +- `brew` - use `HOMEBREW_NO_AUTO_UPDATE=1` env var + + +## Beads Issue Tracker + +This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands. + +### Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work +bd close # Complete work +``` + +### Rules + +- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists +- Run `bd prime` for detailed command reference and session close protocol +- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files + +**Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns. + +## Session Completion + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..cd553b9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,70 @@ +# Project Instructions for AI Agents + +This file provides instructions and context for AI coding agents working on this project. + + +## Beads Issue Tracker + +This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands. + +### Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work +bd close # Complete work +``` + +### Rules + +- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists +- Run `bd prime` for detailed command reference and session close protocol +- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files + +**Architecture in one line:** issues live in a local Dolt DB; sync uses `refs/dolt/data` on your git remote; `.beads/issues.jsonl` is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns. + +## Session Completion + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds + + + +## Build & Test + +_Add your build and test commands here_ + +```bash +# Example: +# npm install +# npm test +``` + +## Architecture Overview + +_Add a brief overview of your project architecture_ + +## Conventions & Patterns + +_Add your project-specific conventions here_ From d4e49cde1e89715021b7fb83cfb4139dd61ae18a Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Sun, 24 May 2026 10:59:57 -0400 Subject: [PATCH 2/2] build wkwebview mvp shell --- Dreamio.xcodeproj/project.pbxproj | 319 ++++++++++++++++++ Dreamio/AppDelegate.swift | 19 ++ Dreamio/DreamioWebViewController.swift | 140 ++++++++ Dreamio/Info.plist | 38 +++ Dreamio/SceneDelegate.swift | 20 ++ README.md | 34 ++ .../2026-05-24-build-wkwebview-mvp-shell.html | 260 ++++++++++++++ 7 files changed, 830 insertions(+) create mode 100644 Dreamio.xcodeproj/project.pbxproj create mode 100644 Dreamio/AppDelegate.swift create mode 100644 Dreamio/DreamioWebViewController.swift create mode 100644 Dreamio/Info.plist create mode 100644 Dreamio/SceneDelegate.swift create mode 100644 README.md create mode 100644 docs/turns/2026-05-24-build-wkwebview-mvp-shell.html diff --git a/Dreamio.xcodeproj/project.pbxproj b/Dreamio.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5324de1 --- /dev/null +++ b/Dreamio.xcodeproj/project.pbxproj @@ -0,0 +1,319 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 6F2A2B362C00100100DREAMIO /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2A2B332C00100100DREAMIO /* AppDelegate.swift */; }; + 6F2A2B372C00100100DREAMIO /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2A2B342C00100100DREAMIO /* SceneDelegate.swift */; }; + 6F2A2B382C00100100DREAMIO /* DreamioWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2A2B352C00100100DREAMIO /* DreamioWebViewController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 6F2A2B302C00100100DREAMIO /* Dreamio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Dreamio.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6F2A2B332C00100100DREAMIO /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 6F2A2B342C00100100DREAMIO /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 6F2A2B352C00100100DREAMIO /* DreamioWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DreamioWebViewController.swift; sourceTree = ""; }; + 6F2A2B392C00100100DREAMIO /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6F2A2B2D2C00100100DREAMIO /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6F2A2B272C00100100DREAMIO = { + isa = PBXGroup; + children = ( + 6F2A2B322C00100100DREAMIO /* Dreamio */, + 6F2A2B312C00100100DREAMIO /* Products */, + ); + sourceTree = ""; + }; + 6F2A2B312C00100100DREAMIO /* Products */ = { + isa = PBXGroup; + children = ( + 6F2A2B302C00100100DREAMIO /* Dreamio.app */, + ); + name = Products; + sourceTree = ""; + }; + 6F2A2B322C00100100DREAMIO /* Dreamio */ = { + isa = PBXGroup; + children = ( + 6F2A2B332C00100100DREAMIO /* AppDelegate.swift */, + 6F2A2B342C00100100DREAMIO /* SceneDelegate.swift */, + 6F2A2B352C00100100DREAMIO /* DreamioWebViewController.swift */, + 6F2A2B392C00100100DREAMIO /* Info.plist */, + ); + path = Dreamio; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6F2A2B2F2C00100100DREAMIO /* Dreamio */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6F2A2B412C00100100DREAMIO /* Build configuration list for PBXNativeTarget "Dreamio" */; + buildPhases = ( + 6F2A2B2C2C00100100DREAMIO /* Sources */, + 6F2A2B2D2C00100100DREAMIO /* Frameworks */, + 6F2A2B2E2C00100100DREAMIO /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Dreamio; + productName = Dreamio; + productReference = 6F2A2B302C00100100DREAMIO /* Dreamio.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6F2A2B282C00100100DREAMIO /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1600; + TargetAttributes = { + 6F2A2B2F2C00100100DREAMIO = { + CreatedOnToolsVersion = 16.0; + }; + }; + }; + buildConfigurationList = 6F2A2B2B2C00100100DREAMIO /* Build configuration list for PBXProject "Dreamio" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6F2A2B272C00100100DREAMIO; + productRefGroup = 6F2A2B312C00100100DREAMIO /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6F2A2B2F2C00100100DREAMIO /* Dreamio */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6F2A2B2E2C00100100DREAMIO /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6F2A2B2C2C00100100DREAMIO /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6F2A2B362C00100100DREAMIO /* AppDelegate.swift in Sources */, + 6F2A2B372C00100100DREAMIO /* SceneDelegate.swift in Sources */, + 6F2A2B382C00100100DREAMIO /* DreamioWebViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 6F2A2B3A2C00100100DREAMIO /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 6F2A2B3B2C00100100DREAMIO /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6F2A2B3E2C00100100DREAMIO /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = Dreamio/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kell.dreamio; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6F2A2B3F2C00100100DREAMIO /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = Dreamio/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kell.dreamio; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6F2A2B2B2C00100100DREAMIO /* Build configuration list for PBXProject "Dreamio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6F2A2B3A2C00100100DREAMIO /* Debug */, + 6F2A2B3B2C00100100DREAMIO /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6F2A2B412C00100100DREAMIO /* Build configuration list for PBXNativeTarget "Dreamio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6F2A2B3E2C00100100DREAMIO /* Debug */, + 6F2A2B3F2C00100100DREAMIO /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6F2A2B282C00100100DREAMIO /* Project object */; +} diff --git a/Dreamio/AppDelegate.swift b/Dreamio/AppDelegate.swift new file mode 100644 index 0000000..66854de --- /dev/null +++ b/Dreamio/AppDelegate.swift @@ -0,0 +1,19 @@ +import UIKit + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + true + } + + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } +} diff --git a/Dreamio/DreamioWebViewController.swift b/Dreamio/DreamioWebViewController.swift new file mode 100644 index 0000000..6d0376a --- /dev/null +++ b/Dreamio/DreamioWebViewController.swift @@ -0,0 +1,140 @@ +import UIKit +import WebKit + +final class DreamioWebViewController: UIViewController { + private enum Constants { + static let stremioWebURL = URL(string: "https://web.stremio.com/")! + } + + private lazy var webView: WKWebView = { + let configuration = WKWebViewConfiguration() + configuration.defaultWebpagePreferences.allowsContentJavaScript = true + configuration.allowsInlineMediaPlayback = true + configuration.mediaTypesRequiringUserActionForPlayback = [] + configuration.preferences.javaScriptCanOpenWindowsAutomatically = true + + let webView = WKWebView(frame: .zero, configuration: configuration) + webView.translatesAutoresizingMaskIntoConstraints = false + webView.allowsBackForwardNavigationGestures = true + webView.customUserAgent = "Dreamio/0.1 WKWebView" + webView.navigationDelegate = self + webView.uiDelegate = self + webView.scrollView.contentInsetAdjustmentBehavior = .never + return webView + }() + + private let progressView: UIProgressView = { + let view = UIProgressView(progressViewStyle: .bar) + view.translatesAutoresizingMaskIntoConstraints = false + view.tintColor = UIColor(red: 0.55, green: 0.35, blue: 0.95, alpha: 1.0) + return view + }() + + private var progressObservation: NSKeyValueObservation? + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .systemBackground + view.addSubview(webView) + view.addSubview(progressView) + + NSLayoutConstraint.activate([ + webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + webView.topAnchor.constraint(equalTo: view.topAnchor), + webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + progressView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + progressView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + progressView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor) + ]) + + progressObservation = webView.observe(\.estimatedProgress, options: [.new]) { [weak self] webView, _ in + self?.updateProgress(webView.estimatedProgress) + } + + loadDreamio() + } + + private func loadDreamio() { + let request = URLRequest(url: Constants.stremioWebURL) + webView.load(request) + } + + private func updateProgress(_ progress: Double) { + progressView.isHidden = progress >= 1.0 + progressView.setProgress(Float(progress), animated: true) + } + + private func showLoadFailure(_ error: Error) { + let alert = UIAlertController( + title: "Could not load Dreamio", + message: error.localizedDescription, + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "Retry", style: .default) { [weak self] _ in + self?.loadDreamio() + }) + present(alert, animated: true) + } +} + +extension DreamioWebViewController: WKNavigationDelegate { + func webView( + _ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void + ) { + guard let url = navigationAction.request.url else { + decisionHandler(.cancel) + return + } + + if shouldOpenExternally(url: url, navigationType: navigationAction.navigationType) { + UIApplication.shared.open(url) + decisionHandler(.cancel) + return + } + + decisionHandler(.allow) + } + + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + showLoadFailure(error) + } + + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + showLoadFailure(error) + } + + private func shouldOpenExternally(url: URL, navigationType: WKNavigationType) -> Bool { + guard let scheme = url.scheme?.lowercased() else { + return false + } + + if ["http", "https"].contains(scheme) { + return false + } + + if ["mailto", "tel", "sms"].contains(scheme) { + return true + } + + return navigationType == .linkActivated + } +} + +extension DreamioWebViewController: WKUIDelegate { + func webView( + _ webView: WKWebView, + createWebViewWith configuration: WKWebViewConfiguration, + for navigationAction: WKNavigationAction, + windowFeatures: WKWindowFeatures + ) -> WKWebView? { + if navigationAction.targetFrame == nil { + webView.load(navigationAction.request) + } + + return nil + } +} diff --git a/Dreamio/Info.plist b/Dreamio/Info.plist new file mode 100644 index 0000000..9451a6a --- /dev/null +++ b/Dreamio/Info.plist @@ -0,0 +1,38 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Dreamio/SceneDelegate.swift b/Dreamio/SceneDelegate.swift new file mode 100644 index 0000000..2781eac --- /dev/null +++ b/Dreamio/SceneDelegate.swift @@ -0,0 +1,20 @@ +import UIKit + +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene else { + return + } + + let window = UIWindow(windowScene: windowScene) + window.rootViewController = DreamioWebViewController() + window.makeKeyAndVisible() + self.window = window + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..d18fe91 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Dreamio + +Dreamio is a minimal iOS `WKWebView` wrapper around hosted Stremio Web. + +The MVP intentionally keeps native code thin. It loads `https://web.stremio.com/` +inside a UIKit host app, handles new-window navigation in the existing web view, +allows inline media playback, and leaves playback viability to real-device +testing. + +## Running the MVP + +1. Open `Dreamio.xcodeproj` in Xcode. +2. Select the `Dreamio` scheme. +3. Pick a real iPhone or iPad device. +4. Set a development team for code signing if Xcode asks. +5. Build and run. + +The repository machine currently has Command Line Tools selected instead of full +Xcode, so command-line `xcodebuild` validation is not available here. + +## MVP Validation Checklist + +- Cold launch loads hosted Stremio Web. +- Login completes and persists after app relaunch. +- Catalog and library navigation work. +- Addon install or configuration flows work, including redirects or popups. +- HLS direct stream playback works. +- MP4 direct stream playback works. +- Unsupported formats fail understandably. +- Fullscreen, rotation, pause/resume, and background/foreground behavior are + acceptable for v1. + +Track playback results by device, iOS version, stream protocol, container, +codec, subtitle type, HTTP status, and WebKit media error when available. diff --git a/docs/turns/2026-05-24-build-wkwebview-mvp-shell.html b/docs/turns/2026-05-24-build-wkwebview-mvp-shell.html new file mode 100644 index 0000000..6ed5185 --- /dev/null +++ b/docs/turns/2026-05-24-build-wkwebview-mvp-shell.html @@ -0,0 +1,260 @@ + + + + + + Dreamio WKWebView MVP Shell + + + + +
+
+
Repository implementation turn
+

Dreamio WKWebView MVP shell

+

Created the first runnable Dreamio app shape: a thin iOS UIKit host with a single WKWebView that loads hosted Stremio Web, handles popup-style navigation in place, allows inline media playback, and gives the user a concrete real-device validation checklist.

+
+ +
+

Summary

+

The repository moved from planning-only files to an MVP iOS app scaffold. The app is intentionally narrow: it exists to prove whether hosted Stremio Web can support login, browsing, addon flows, and direct playback inside WKWebView on real iPhone and iPad hardware.

+
+ +
+

Changes Made

+
    +
  • Added Dreamio.xcodeproj with a single iOS application target.
  • +
  • Added a UIKit lifecycle with AppDelegate and SceneDelegate.
  • +
  • Added DreamioWebViewController, which loads https://web.stremio.com/ in a configured WKWebView.
  • +
  • Enabled inline media playback, JavaScript window opening, back-forward gestures, and a small load progress indicator.
  • +
  • Added basic external URL handling and in-place handling for new-window flows.
  • +
  • Added README.md with run instructions and the MVP validation checklist.
  • +
+
+ +
+

Context

+

The source plan in /Users/kell/dreamio-plan.md recommends starting with hosted Stremio Web before bundling local assets. This implementation follows that gate exactly: no local Stremio Web fork, no native player bridge, no torrent engine, and no App Store positioning work.

+
+ +
+

Important Implementation Details

+
    +
  • The MVP URL is centralized in DreamioWebViewController.Constants.stremioWebURL.
  • +
  • WKUIDelegate loads targetless popup requests in the existing web view so auth or addon flows do not vanish.
  • +
  • Non-HTTP user-activated links such as mailto:, tel:, and sms: open externally.
  • +
  • Orientation support includes portrait and landscape on iPhone, plus upside-down portrait on iPad.
  • +
  • Code signing is left automatic with an empty development team, so Xcode can prompt for the correct personal or team signing identity.
  • +
+
+ +
+

Relevant Diff Snippets

+

The snippets below are rendered with Diffs when the document has network access, with plain-code fallback content retained in the page.

+
+
+++ Dreamio/DreamioWebViewController.swift
++ WKWebView configuration loads https://web.stremio.com/
++ Inline media playback is enabled.
++ Popup/new-window requests are loaded in the existing web view.
++ Basic load failure UI offers Retry.
+
+
+++ README.md
++ Added Xcode run instructions.
++ Added hosted web, login, addon, HLS, MP4, fullscreen, rotation, and relaunch validation checklist.
+
+ +
+

Expected Impact for End-Users

+

The user can now open the project in Xcode, install it on a real iOS device, and start testing whether Stremio Web is viable inside a private Dreamio shell. The app should feel like a focused wrapper, not a rewritten media application.

+
+ +
+

Validation

+
    +
  • Ran plutil -lint Dreamio/Info.plist: passed.
  • +
  • Ran plutil -lint Dreamio.xcodeproj/project.pbxproj: passed.
  • +
  • Checked local Swift availability with swift --version: available.
  • +
  • Attempted to check Xcode build availability with xcodebuild -version: blocked because the active developer directory is Command Line Tools, not full Xcode.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • The app has not been compiled on this machine because full Xcode is not selected. Mitigation: open the project in Xcode or switch xcode-select to a full Xcode install, then build on device.
  • +
  • No real-device playback gate has been passed yet. Mitigation: use the README checklist and record stream failures by protocol, container, codec, subtitles, HTTP status, and WebKit error.
  • +
  • Beads Dolt sync could not pull because no remote is configured. Mitigation: this is documented in the handoff, and the local issue was still created and updated.
  • +
+
+ +
+

Follow-up Work

+
    +
  • Run the app on a real iPhone and iPad, then update the validation checklist with concrete results.
  • +
  • Add a small diagnostics screen or log export if WebKit playback failures are hard to capture manually.
  • +
  • Only after hosted viability passes, pin and evaluate bundled stremio-web assets.
  • +
+
+
+ +