Skip to content

feat(macos): add OpenWorlds product shell - #123

Closed
100yenadmin wants to merge 1 commit into
mainfrom
macos/openworlds-product-shell
Closed

feat(macos): add OpenWorlds product shell#123
100yenadmin wants to merge 1 commit into
mainfrom
macos/openworlds-product-shell

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 25, 2026

Copy link
Copy Markdown
Member

Summary

Refs #113, #114, #122.

This PR starts translating the OpenWorlds design bundle into ClawDnD's native macOS app without importing third-party/reference assets or changing engine state semantics.

  • Adds an OpenWorlds-inspired SwiftUI token/component layer for parchment, walnut, brass, ink, pills, panels, ornaments, dividers, and brass buttons.
  • Replaces the plain native split shell with a CRPG-style walnut frame, compact icon rail, parchment detail stage, title strip, and themed status strip.
  • Turns the Play empty state into a Chronicles launcher that reads local campaign summaries, previews the selected run, resumes via the existing viewer, and starts provider-backed play through the existing app process service.
  • Tightens title-bar truthfulness so it only shows an active campaign when activeCampaignID resolves; stopping the viewer clears the active campaign marker.
  • Adds docs/OPENWORLDS_DESIGN_ASSET_POLICY.md with explicit audited source buckets for styles.css, *.jsx, data.js, screenshots, uploaded references, and handoff metadata.

Architecture Notes

The macOS app remains an orchestrator/read surface. This PR does not add engine writes, rules changes, provider protocol changes, story/QA content, world seeds, or asset ingestion.

State boundaries:

  • Resume Chronicle still calls AppProcessService.startViewer(...) with stateDir and campaignID.
  • Start Game still calls AppProcessService.startProviderSession(...).
  • No SwiftUI view writes play-state, qa/state, snapshots, or provider runtime files directly.
  • The new asset policy explicitly marks Owlcat-style screenshots and uploads/*.png as reference-only/do-not-commit.

Design Source

Local scratch bundle:

  • /Volumes/LEXAR/Codex/openworlds-design-2026-05-25/
  • /Volumes/LEXAR/Codex/openworlds-design-2026-05-25/clawdnd-mac-app-gap-map.md
  • /Volumes/LEXAR/Codex/openworlds-design-2026-05-25/clawdnd-openworlds-shell-smoke-2.png

No third-party/reference image assets were copied into this PR.

Files To Review First

  • macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsTheme.swift
  • macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsComponents.swift
  • macos/ClawDnDApp/Sources/ClawDnDApp/Views/RootView.swift
  • macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift
  • docs/OPENWORLDS_DESIGN_ASSET_POLICY.md

Validation

Ran locally from /Volumes/LEXAR/repos/ClawDnD-openworlds-product-shell:

swift build --package-path macos/ClawDnDApp
bash -n scripts/launch_common.sh scripts/play.sh scripts/play_party.sh clawdnd-play.command script/build_and_run.sh
python3 -m py_compile viewer/server.py
python3 scripts/license_check.py
git diff --cached --check
git diff --check
./script/build_and_run.sh --verify
codesign --verify --deep --strict dist/ClawDnD.app
plutil -lint dist/ClawDnD.app/Contents/Info.plist

Also ran a visual smoke capture after --verify:

  • /Volumes/LEXAR/Codex/openworlds-design-2026-05-25/clawdnd-openworlds-shell-smoke-2.png

Adversarial Review

Two read-only subagents reviewed the uncommitted diff before publication.

Fixed before this PR:

  • Launcher layout overflow at the app minimum width: replaced rigid three-column layout with ViewThatFits and compacted column widths.
  • Title bar overclaiming active campaign: removed fallback to newest catalogued campaign and clear activeCampaignID on viewer stop.
  • [design][license] Audit OpenWorlds/Owlcat reference assets before visual PRs #122 audit was not bucketed enough: added an explicit audited source bucket table.
  • Sidebar custom rail lacked accessibility selected state: added accessibility label/value for selection state.
  • git diff --check EOF whitespace warnings: fixed.

Clearances from the reviews:

  • No direct play-state / qa/state / snapshot writes from visual code.
  • No reference screenshots/zips/assets staged.
  • Existing Play, Campaigns, Monitor, Providers, Settings, and Logs routes remain present.
  • Swift build and license checks passed.

Summary by CodeRabbit

  • New Features

    • Redesigned campaign launcher interface with improved layout and visual organization
    • New sidebar navigation with themed styling and section-based navigation
    • Added campaign-aware title bar displaying current campaign and location details
    • Implemented comprehensive design theme system with new visual components and styling
  • Documentation

    • Added design asset policy documentation

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces the OpenWorlds design system—a complete theme library with color palette, reusable SwiftUI components, and integrated campaign launcher—alongside AppProcessService cleanup and design governance documentation. The implementation flows from foundational theme constants through composed UI components into updated root and play views.

Changes

OpenWorlds UI Design System

Layer / File(s) Summary
Theme foundation with colors and backgrounds
macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsTheme.swift
OpenWorldsTheme enum establishes parchment, walnut, brass, ink color constants and geometry dimensions. Color.init(hex:opacity:) converts hex values to sRGB. OpenWorldsWindowBackground and OpenWorldsParchmentBackground compose layered gradients.
Component library
macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsComponents.swift
OpenWorldsPanel wraps content in padded container with optional header, corner ornaments, border, and shadow. OpenWorldsDivider and Diamond shape provide themed dividers. OpenWorldsCornerOrnament renders decorative corner paths. OpenWorldsPill and OpenWorldsBrassButtonStyle provide tone-driven label and button styling with gradient backgrounds.
Root layout with title bar and sidebar
macos/ClawDnDApp/Sources/ClawDnDApp/Views/RootView.swift
RootView.body replaces NavigationSplitView with custom stacked layout. New OpenWorldsTitleBar displays CLAWDND brand, campaign/day/location, and status jewels. SidebarView changes from List to custom icon/title buttons with selection-driven styling. StatusStrip adds themed colors and separator line.
PlayView state and campaign lifecycle
macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift (state/lifecycle)
PlayView adds campaignStore environment object, selectedCampaignID, and error state. onAppear and onChange hooks auto-load campaigns and select first if needed. selectFirstCampaignIfNeeded() maintains selection consistency.
PlayView control bar and campaign launcher
macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift (UI/components)
Control bar refactored to HStack "The Table" with provider selection and action buttons. New ChroniclesLauncherSurface renders campaign list, preview grid, and launch panel. ChronicleCard provides selectable rows. openCampaign clears errors and triggers viewer startup.
Provider session runID normalization
macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift (startProvider)
startProvider() computes resolvedRunID (generates UUID if empty/trimmed), updates state, and passes to processService.startProviderSession.
AppProcessService viewer cleanup
macos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swift
stopViewer() now clears activeCampaignID alongside existing endpoint shutdown.
Design asset governance
docs/OPENWORLDS_DESIGN_ASSET_POLICY.md
Policy document specifies allowed uses (layout ideas, asset generation with docs, local scratch refs), prohibited actions (committing binaries/images, reusing third-party art, promoting prototypes, UI state-writing), and PR checklist (asset statements, directory scoping, license check script, git status validation).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • 100yenadmin/ClawDnD#113: Directly implements OpenWorlds theme enum, color palette, panel/component library, title bar, and sidebar button styles proposed in this issue.
  • 100yenadmin/ClawDnD#114: Implements ChroniclesLauncherSurface, ChronicleCard campaign browser, AppProcessService viewer cleanup, and related Play/Root view integration described in launcher issue.
  • 100yenadmin/ClawDnD#122: Directly implements OpenWorlds design asset policy, license check audit guidance, and PR checklist validation proposed in this governance issue.
  • 100yenadmin/ClawDnD#82: Changes to PlayView, RootView, AppProcessService, and new OpenWorlds UI components implement native macOS app shell and provider/play surface described in epic.

Possibly related PRs

  • 100yenadmin/ClawDnD#83: Both PRs modify core SwiftUI orchestration in RootView, PlayView, and AppProcessService; this PR's UI/campaign launcher builds directly on the app-shell foundation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: introducing an OpenWorlds-themed product shell for macOS with SwiftUI components and visual design tokens.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • LINEAR integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
macos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swift (1)

252-258: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear activeCampaignID in the viewer termination handler for consistency.

The explicit stopViewer() call now clears activeCampaignID (line 106), but when the viewer process exits naturally or crashes, the termination handler updates viewerEndpoint.status without clearing activeCampaignID. This creates an inconsistency where the UI may show a stale active campaign after an unexpected viewer exit.

🔄 Proposed fix to maintain state consistency
 } else if self?.viewerProcess === managed {
     self?.viewerProcess = nil
+    self?.activeCampaignID = nil
     if var endpoint = self?.viewerEndpoint {
         endpoint.status = .stopped
         self?.viewerEndpoint = endpoint
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swift` around
lines 252 - 258, When handling the viewer process termination in the block that
checks `self?.viewerProcess === managed`, also clear the stale campaign state:
set `self?.activeCampaignID = nil` (or call the existing `stopViewer()` logic)
alongside setting `self?.viewerProcess = nil` and updating
`viewerEndpoint.status = .stopped`, ensuring `activeCampaignID` is cleared the
same way as when `stopViewer()` is invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift`:
- Around line 34-40: Remove the redundant call to selectFirstCampaignIfNeeded
from the View's .onAppear block because campaignStore.reload (invoked there) is
asynchronous and the onChange(of: campaignStore.campaigns) already handles
selecting when campaigns finish loading; edit the PlayView.swift onAppear
closure to only call campaignStore.reload(repoPath: repoPath) and leave
selectFirstCampaignIfNeeded only in the onChange handler so selection happens
after campaigns update.
- Around line 238-240: Change the computed property selectedCampaign to return
nil when there is no matching campaign instead of falling back to
campaigns.first; specifically remove the "?? campaigns.first" fallback in the
selectedCampaign getter (which currently compares $0.id to selectedCampaignID),
rely on selectFirstCampaignIfNeeded to ensure consistent selection, and update
campaignPreview to handle a nil selectedCampaign explicitly (e.g., show "No
campaign selected") so stale selectedCampaignID cases aren't silently masked.

In `@macos/ClawDnDApp/Sources/ClawDnDApp/Views/RootView.swift`:
- Around line 49-52: The currentCampaign computed property falls back to
campaignStore.campaigns.first when processService.activeCampaignID is set but
missing, which can surface stale/incorrect campaign info; change currentCampaign
to only return a match for processService.activeCampaignID
(campaignStore.campaigns.first { $0.id == processService.activeCampaignID }) and
otherwise return nil so the title shows "No Chronicle Selected" instead of a
fallback campaign.

---

Outside diff comments:
In `@macos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swift`:
- Around line 252-258: When handling the viewer process termination in the block
that checks `self?.viewerProcess === managed`, also clear the stale campaign
state: set `self?.activeCampaignID = nil` (or call the existing `stopViewer()`
logic) alongside setting `self?.viewerProcess = nil` and updating
`viewerEndpoint.status = .stopped`, ensuring `activeCampaignID` is cleared the
same way as when `stopViewer()` is invoked.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1c7a903b-4fe1-4f48-b819-a499ee7a7e1a

📥 Commits

Reviewing files that changed from the base of the PR and between ed64382 and f694288.

📒 Files selected for processing (6)
  • docs/OPENWORLDS_DESIGN_ASSET_POLICY.md
  • macos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swift
  • macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsComponents.swift
  • macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsTheme.swift
  • macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift
  • macos/ClawDnDApp/Sources/ClawDnDApp/Views/RootView.swift
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (2)
macos/ClawDnDApp/Sources/ClawDnDApp/Views/RootView.swift (1)
macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsComponents.swift (1)
  • background (193-203)
macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift (2)
macos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swift (4)
  • startViewer (53-97)
  • stopProvider (192-205)
  • stopViewer (99-107)
  • startProviderSession (109-190)
macos/ClawDnDApp/Sources/ClawDnDApp/Services/CampaignStore.swift (1)
  • reload (10-31)
🪛 LanguageTool
docs/OPENWORLDS_DESIGN_ASSET_POLICY.md

[uncategorized] ~22-~22: The operating system from Apple is written “macOS”.
Context: ... visual/design implementation scoped to macos/, viewer/, docs/, or build scripts...

(MAC_OS)

🔇 Additional comments (17)
docs/OPENWORLDS_DESIGN_ASSET_POLICY.md (1)

1-25: LGTM!

macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsTheme.swift (1)

1-94: LGTM!

macos/ClawDnDApp/Sources/ClawDnDApp/Views/Design/OpenWorldsComponents.swift (1)

1-205: LGTM!

macos/ClawDnDApp/Sources/ClawDnDApp/Services/AppProcessService.swift (1)

106-106: LGTM!

macos/ClawDnDApp/Sources/ClawDnDApp/Views/RootView.swift (4)

23-47: LGTM!


118-165: LGTM!


167-219: LGTM!


221-260: LGTM!

macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift (9)

3-23: LGTM!


48-88: LGTM!


90-108: LGTM!


110-136: LGTM!


138-150: LGTM!


165-185: LGTM!


204-209: LGTM!


279-413: LGTM!


415-464: LGTM!

Comment on lines +34 to +40
.onAppear {
campaignStore.reload(repoPath: repoPath)
selectFirstCampaignIfNeeded()
}
.onChange(of: campaignStore.campaigns) { _ in
selectFirstCampaignIfNeeded()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove redundant selectFirstCampaignIfNeeded call in onAppear.

campaignStore.reload is async (uses Task.detached), so the selectFirstCampaignIfNeeded call on Line 36 runs before campaigns are loaded. The onChange handler (Lines 38-40) already ensures selection when campaigns update, making the onAppear call redundant.

♻️ Proposed fix
 .onAppear {
     campaignStore.reload(repoPath: repoPath)
-    selectFirstCampaignIfNeeded()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.onAppear {
campaignStore.reload(repoPath: repoPath)
selectFirstCampaignIfNeeded()
}
.onChange(of: campaignStore.campaigns) { _ in
selectFirstCampaignIfNeeded()
}
.onAppear {
campaignStore.reload(repoPath: repoPath)
}
.onChange(of: campaignStore.campaigns) { _ in
selectFirstCampaignIfNeeded()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift` around lines 34 -
40, Remove the redundant call to selectFirstCampaignIfNeeded from the View's
.onAppear block because campaignStore.reload (invoked there) is asynchronous and
the onChange(of: campaignStore.campaigns) already handles selecting when
campaigns finish loading; edit the PlayView.swift onAppear closure to only call
campaignStore.reload(repoPath: repoPath) and leave selectFirstCampaignIfNeeded
only in the onChange handler so selection happens after campaigns update.

Comment on lines +238 to +240
private var selectedCampaign: CampaignSummary? {
campaigns.first { $0.id == selectedCampaignID } ?? campaigns.first
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider removing fallback to .first for clarity.

The fallback ?? campaigns.first could mask issues where selectedCampaignID is stale. Since selectFirstCampaignIfNeeded maintains selection consistency, this fallback should rarely trigger. Returning nil explicitly would make the no-selection case clearer in the UI (e.g., campaignPreview shows "No campaign selected").

♻️ Proposed change
 private var selectedCampaign: CampaignSummary? {
-    campaigns.first { $0.id == selectedCampaignID } ?? campaigns.first
+    campaigns.first { $0.id == selectedCampaignID }
 }

Then update campaignPreview to handle nil more explicitly if needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos/ClawDnDApp/Sources/ClawDnDApp/Views/PlayView.swift` around lines 238 -
240, Change the computed property selectedCampaign to return nil when there is
no matching campaign instead of falling back to campaigns.first; specifically
remove the "?? campaigns.first" fallback in the selectedCampaign getter (which
currently compares $0.id to selectedCampaignID), rely on
selectFirstCampaignIfNeeded to ensure consistent selection, and update
campaignPreview to handle a nil selectedCampaign explicitly (e.g., show "No
campaign selected") so stale selectedCampaignID cases aren't silently masked.

Comment on lines +49 to +52
private var currentCampaign: CampaignSummary? {
campaignStore.campaigns.first { $0.id == processService.activeCampaignID }
?? campaignStore.campaigns.first
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fallback to .first may display stale campaign data.

When activeCampaignID is set but doesn't match any loaded campaign, falling back to campaigns.first could show incorrect campaign info in the title bar. Better to return nil so the title bar shows "No Chronicle Selected".

🔧 Proposed fix
 private var currentCampaign: CampaignSummary? {
-    campaignStore.campaigns.first { $0.id == processService.activeCampaignID }
-        ?? campaignStore.campaigns.first
+    guard let activeCampaignID = processService.activeCampaignID else {
+        return nil
+    }
+    return campaignStore.campaigns.first { $0.id == activeCampaignID }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private var currentCampaign: CampaignSummary? {
campaignStore.campaigns.first { $0.id == processService.activeCampaignID }
?? campaignStore.campaigns.first
}
private var currentCampaign: CampaignSummary? {
guard let activeCampaignID = processService.activeCampaignID else {
return nil
}
return campaignStore.campaigns.first { $0.id == activeCampaignID }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos/ClawDnDApp/Sources/ClawDnDApp/Views/RootView.swift` around lines 49 -
52, The currentCampaign computed property falls back to
campaignStore.campaigns.first when processService.activeCampaignID is set but
missing, which can surface stale/incorrect campaign info; change currentCampaign
to only return a match for processService.activeCampaignID
(campaignStore.campaigns.first { $0.id == processService.activeCampaignID }) and
otherwise return nil so the title shows "No Chronicle Selected" instead of a
fallback campaign.

@100yenadmin
100yenadmin marked this pull request as draft May 25, 2026 17:48
@100yenadmin

Copy link
Copy Markdown
Member Author

Parking this PR as draft after visual review. The SwiftUI approximation does not preserve the OpenWorlds export closely enough to be a merge candidate. Next step is a fidelity-first planning sprint: treat the exported OpenWorlds HTML/JSX/CSS as the visual contract, decide the production integration architecture, then replace or substantially rework this PR from that plan. No merge until the new plan has screenshot-level validation.

@100yenadmin

Copy link
Copy Markdown
Member Author

Supersession note: PR #124 is now open with the OpenWorlds fidelity-first contract. This draft PR should remain unmerged; its SwiftUI repaint direction is superseded by the audited viewer-hosted OpenWorlds surface path. Per the new plan, this PR can be closed as superseded once the first viewer-hosted /openworlds/ replacement PR is open.

@100yenadmin

Copy link
Copy Markdown
Member Author

Superseded by the fidelity-first viewer-hosted OpenWorlds route in #124 and #125. Keeping this SwiftUI repaint branch unmerged; future native work should host /openworlds/ in WKWebView and only revive a native repaint with screenshot-level parity evidence.

@100yenadmin

Copy link
Copy Markdown
Member Author

Closing as superseded by #124 and #125. The replacement path preserves the exported OpenWorlds surface under the viewer route instead of repainting it in SwiftUI.

@100yenadmin

Copy link
Copy Markdown
Member Author

Superseded by the OpenWorlds native-app correction path. The visible app shell should be the viewer-hosted OpenWorlds UI, with Swift/AppKit acting as process supervisor and native bridge. The corrected roadmap is now reflected in #82, #113, #114, #131, #132, and #133; this draft should not merge as the product UI direction.

@100yenadmin

Copy link
Copy Markdown
Member Author

Superseded by #135. The correction path makes OpenWorlds the visible native app shell and keeps SwiftUI/AppKit as the supervisor/bridge layer instead of repainting the UI in the old Swift shell.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant