Skip to content

Commit 2fd6f1d

Browse files
committed
Add browser cloud storage provider integration
1 parent 26160c0 commit 2fd6f1d

File tree

53 files changed

+2781
-366
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2781
-366
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ Repo-specific design rules:
296296
- Keep UI routes in shared route constants and keep `data-testid` names in shared UI contract constants.
297297
- Keep UI flow logic, keyboard shortcuts, DOM ids/selectors, and reusable UI constants in C#/Blazor contracts whenever the platform allows it; use JS only for unavoidable browser API interop or DOM access that Blazor cannot own directly.
298298
- Prefer deleting JS files entirely when they only hold product UI behavior or duplicated constants; JS modules may exist only as thin bridges to browser APIs or external JS SDKs, with the owning workflow and state kept in C#/Blazor.
299+
- For standalone cloud-storage integrations, persist provider keys, tokens, and connection metadata in browser `localStorage`; do not introduce server-side secret storage for runtime auth in this app shape.
299300
- Third-party runtime JavaScript SDKs MUST be sourced only from explicitly pinned GitHub Release tags and assets, copied into the repo, bundled locally with their runtime dependencies, and never loaded from CDNs, package registries, `latest` endpoints, or ad-hoc remote downloads at app runtime.
300301
- Repo-owned manifests, scripts, workflows, and project files that track third-party runtime JavaScript SDKs MUST point to concrete GitHub release versions and asset URLs, never floating references.
301302
- Any vendored runtime JavaScript SDK that tracks an upstream GitHub repo MUST have an automated watcher job that checks new GitHub releases and opens a repo issue describing the required update when a newer release appears.

Directory.Packages.props

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
55
<ItemGroup>
6+
<PackageVersion Include="Azure.Identity" Version="1.20.0" />
67
<PackageVersion Include="bunit" Version="2.6.2" />
78
<PackageVersion Include="coverlet.collector" Version="8.0.1" />
9+
<PackageVersion Include="ManagedCode.Storage.Browser" Version="10.0.4" />
10+
<PackageVersion Include="ManagedCode.Storage.CloudKit" Version="10.0.4" />
11+
<PackageVersion Include="ManagedCode.Storage.Dropbox" Version="10.0.4" />
12+
<PackageVersion Include="ManagedCode.Storage.Gcp" Version="10.0.4" />
13+
<PackageVersion Include="ManagedCode.Storage.GoogleDrive" Version="10.0.4" />
14+
<PackageVersion Include="ManagedCode.Storage.OneDrive" Version="10.0.4" />
15+
<PackageVersion Include="ManagedCode.Storage.VirtualFileSystem" Version="10.0.4" />
816
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="10.0.5" />
917
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
1018
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.5" />
@@ -16,4 +24,4 @@
1624
<PackageVersion Include="xunit" Version="2.9.3" />
1725
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
1826
</ItemGroup>
19-
</Project>
27+
</Project>

cloud-storage-vfs-integration.plan.md

Lines changed: 198 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# ADR-0001: Browser VFS And Cloud Storage For Scripts And Settings
2+
3+
Status: Implemented
4+
Date: 2026-04-01
5+
Related Features: [Architecture Overview](/Users/ksemenenko/Developer/PrompterLive/docs/Architecture.md)
6+
7+
## Implementation plan (step-by-step)
8+
9+
- [x] Analyze the existing browser-only storage shape for scripts, folders, and settings.
10+
- [x] Add ManagedCode.Storage packages and register browser storage plus virtual file system in the WASM host.
11+
- [x] Integrate browser storage plus VFS for cloud/import-export flows while keeping the primary script and folder runtime store on authoritative browser JSON/localStorage for product stability.
12+
- [x] Add provider preference and credential persistence for Dropbox, OneDrive, Google Drive, Google Cloud Storage, and CloudKit.
13+
- [x] Add scripts/settings import-export through the configured provider set.
14+
- [x] Add automated component and browser coverage for the new settings flow and storage contracts.
15+
- [x] Update architecture documentation and record this ADR.
16+
17+
## Context
18+
19+
- `PrompterLive` is a standalone Blazor WebAssembly app with no backend runtime.
20+
- Scripts and folders were persisted through ad-hoc browser localStorage JSON blobs.
21+
- The product now needs browser-local persistence plus optional cloud import/export for scripts and settings.
22+
- The user explicitly wants Managed Code storage providers, browser localStorage for provider keys and metadata, and no server-side secret store.
23+
- Future video-stream export is expected, but this task must stop at scripts and settings.
24+
25+
Goals:
26+
27+
- Keep the app browser-only.
28+
- Add a durable browser-storage boundary for cloud transfers now, while keeping the primary script and folder runtime path stable.
29+
- Let Settings configure real cloud providers for import/export.
30+
- Keep provider credentials and connection metadata in browser `localStorage`.
31+
32+
Non-goals:
33+
34+
- Recorded video upload or archive flows.
35+
- Background sync engines or multi-device conflict resolution.
36+
- OAuth popup/redirect orchestration hosted by PrompterLive.
37+
38+
## Stakeholders
39+
40+
| Role | What they need to know | Questions this ADR must answer |
41+
| --- | --- | --- |
42+
| Product | What users can store now and what is deferred | Can users keep scripts local-first and move them to cloud now? |
43+
| Engineering | Which boundary owns persistence and cloud transfers | Where do browser VFS, provider credentials, and import/export logic live? |
44+
| QA | Which flows must stay green | How do we verify local persistence, provider setup, and import/export safely? |
45+
| Security | Where secrets sit in a backend-free app | Why are provider credentials in browser localStorage, and what risk does that create? |
46+
47+
## Decision
48+
49+
PrompterLive will register `ManagedCode.Storage.Browser` plus `ManagedCode.Storage.VirtualFileSystem` inside the WASM host for browser-local blob storage and provider-backed transfers, while the primary day-to-day script and folder runtime store remains an authoritative browser JSON/localStorage repository. Browser `localStorage` also remains the store for provider credentials, provider metadata, and lightweight settings values. Cloud providers are configured in Settings and currently support snapshot import/export of scripts and settings only.
50+
51+
Key points:
52+
53+
- Primary scripts and folders stay on an authoritative browser JSON/localStorage path so editor/library autosave stays stable under real WASM browser flows.
54+
- Browser storage plus VFS remain active as the local storage abstraction for cloud snapshot import/export and future stream-export expansion.
55+
- Provider credentials stay client-side because the runtime has no backend secret store.
56+
- Cloud import/export is explicit and user-driven, not automatic bidirectional sync.
57+
- The requested Google Cloud Storage provider is implemented through the actual package `ManagedCode.Storage.Gcp`.
58+
59+
## Diagram
60+
61+
```mermaid
62+
flowchart LR
63+
Settings["Settings UI"]
64+
BrowserStore["BrowserCloudStorageStore"]
65+
LocalStorage["browser localStorage<br/>credentials + provider metadata"]
66+
Transfer["CloudStorageTransferService"]
67+
ProviderFactory["CloudStorageProviderFactory"]
68+
LocalRuntime["Browser JSON/localStorage<br/>authoritative scripts + folders"]
69+
LocalVfs["IVirtualFileSystem"]
70+
BrowserProvider["ManagedCode.Storage.Browser<br/>IndexedDB + OPFS"]
71+
Scripts["BrowserScriptRepository"]
72+
Folders["BrowserLibraryFolderRepository"]
73+
Providers["Dropbox / OneDrive / Google Drive / GCS / CloudKit"]
74+
75+
Settings --> BrowserStore
76+
BrowserStore --> LocalStorage
77+
Settings --> Transfer
78+
Transfer --> ProviderFactory
79+
Transfer --> LocalVfs
80+
Scripts --> LocalRuntime
81+
Folders --> LocalRuntime
82+
LocalVfs --> BrowserProvider
83+
ProviderFactory --> Providers
84+
```
85+
86+
## Alternatives considered
87+
88+
### Keep everything in ad-hoc localStorage blobs
89+
90+
- Pros: minimal code churn, no extra package integration.
91+
- Cons: no file model, no VFS abstraction, brittle migration path, harder future expansion to cloud or stream artifacts.
92+
- Rejected because the product now needs a durable storage boundary and provider-backed transfers.
93+
94+
### Introduce a backend for secrets and sync
95+
96+
- Pros: stronger secret handling, server-owned OAuth flows, easier centralized sync.
97+
- Cons: violates the browser-only runtime, adds deployment and operational scope, blocks the current delivery target.
98+
- Rejected because the app must remain standalone WASM.
99+
100+
### Add live cloud sync instead of snapshot import/export
101+
102+
- Pros: tighter cloud parity across devices.
103+
- Cons: conflict resolution, offline semantics, provider-specific edge cases, higher failure surface.
104+
- Rejected because scripts/settings transfer is needed now, while continuous sync is not.
105+
106+
## Consequences
107+
108+
### Positive
109+
110+
- Cloud transfer and future media-export work now have a real browser-storage and VFS abstraction available inside the app boundary.
111+
- Cloud providers can be connected without introducing backend infrastructure.
112+
- Import/export logic is isolated from page markup and provider SDK details.
113+
- The storage boundary is reusable for later video-stream export work without forcing the editor and library to ride a less stable primary persistence path today.
114+
115+
### Negative / risks
116+
117+
- Provider credentials live in browser localStorage and are therefore exposed to the current browser profile.
118+
Mitigation: keep the app backend-free by design, persist only the provider credentials and metadata required for runtime use, and document that these connections are browser-profile scoped.
119+
- Cloud import/export is a snapshot flow, so concurrent edits across devices can overwrite expectations.
120+
Mitigation: keep transfers explicit and user-triggered, not silent background sync.
121+
- Browser test infrastructure must serve static web assets for storage packages as well as app assets.
122+
Mitigation: the UI test harness now reads `staticwebassets.development.json` and maps package `_content/*` assets dynamically.
123+
124+
## Impact
125+
126+
### Code
127+
128+
- Affected modules and services:
129+
- `src/PrompterLive.Shared/AppShell/Services/PrompterLiveServiceCollectionExtensions.cs`
130+
- `src/PrompterLive.Shared/Library/Services/Storage/*`
131+
- `src/PrompterLive.Shared/Storage/*`
132+
- `src/PrompterLive.Shared/Storage/Cloud/*`
133+
- `src/PrompterLive.Shared/Settings/Components/SettingsCloudSection.*`
134+
- `tests/PrompterLive.App.Tests/Settings/*`
135+
- `tests/PrompterLive.App.UITests/Settings/*`
136+
- `tests/PrompterLive.App.UITests/Infrastructure/*`
137+
- New boundaries and responsibilities:
138+
- Browser JSON/localStorage owns primary script and folder runtime persistence.
139+
- Browser storage plus VFS own the local blob boundary used by cloud import/export and future expansion.
140+
- `BrowserCloudStorageStore` owns provider preference and credential persistence.
141+
- `CloudStorageTransferService` owns snapshot import/export.
142+
- `CloudStorageProviderFactory` owns runtime provider construction and validation.
143+
- Feature flags and toggles:
144+
- none
145+
146+
### Data / configuration
147+
148+
- Scripts and folders now materialize into authoritative browser JSON/localStorage payloads with an explicit version marker so seed data and user changes stay stable across reloads.
149+
- Browser storage plus VFS stay registered for provider-backed transfers instead of becoming the primary editor/library persistence path.
150+
- Browser localStorage keys remain the source of truth for provider credentials and metadata.
151+
- No server secrets, environment variables, or backend config were added.
152+
153+
### Documentation
154+
155+
- Architecture docs updated in [docs/Architecture.md](/Users/ksemenenko/Developer/PrompterLive/docs/Architecture.md).
156+
- This ADR is the source of truth for the storage boundary and credential policy.
157+
- Root `AGENTS.md` now records the durable rule that standalone cloud credentials live in browser localStorage.
158+
159+
## Verification
160+
161+
### Objectives
162+
163+
- Prove that local scripts and folders persist through the authoritative browser JSON/localStorage path without editor autosave hangs.
164+
- Prove that cloud provider preferences and credentials persist through browser localStorage.
165+
- Prove that the Settings cloud flow works in a real browser, including reload behavior.
166+
- Prove that the UI-test self-hosted server can boot the app with package static assets present.
167+
168+
### Test environment
169+
170+
- Environment: local browser-hosted WASM runtime and self-hosted Playwright acceptance harness.
171+
- Data and reset strategy: browser-local seed data only; tests rely on isolated runtime contexts.
172+
- External dependencies: no real cloud credentials required for the regression suite; validation paths cover safe failure messages and state persistence.
173+
174+
### Testing methodology
175+
176+
- Core flows and invariants that must be proven:
177+
- scripts and folders persist locally through the authoritative browser repository path
178+
- provider settings survive reload
179+
- the chosen primary cloud provider opens and remains configurable after reload
180+
- invalid provider credentials fail safely with a visible validation message
181+
- Positive flows that must pass:
182+
- component-level persistence for cloud preferences
183+
- browser-level configuration and reload of the Dropbox provider path
184+
- Negative and safe-failure flows that must be covered:
185+
- missing Dropbox credentials do not connect and instead surface validation text
186+
- disconnect removes stored credentials and resets the UI subtitle
187+
- Edge flows that must be covered:
188+
- runtime seed data and existing browser-local payloads can still materialize into the authoritative repositories without resurrecting deleted items
189+
- self-hosted browser suite serves package `_content/*` assets from the static web assets manifest
190+
- Required realism level:
191+
- real browser flow for acceptance
192+
- no mocks or service doubles in verification flows added for this change
193+
- Pass criteria:
194+
- build, relevant tests, full solution tests, coverage, and format all pass
195+
196+
### Test commands
197+
198+
- build: `dotnet build /Users/ksemenenko/Developer/PrompterLive/PrompterLive.slnx -warnaserror`
199+
- test: `dotnet test /Users/ksemenenko/Developer/PrompterLive/PrompterLive.slnx`
200+
- format: `dotnet format /Users/ksemenenko/Developer/PrompterLive/PrompterLive.slnx`
201+
- coverage: `dotnet test /Users/ksemenenko/Developer/PrompterLive/PrompterLive.slnx --collect:"XPlat Code Coverage"`
202+
203+
### New or changed tests
204+
205+
| ID | Scenario | Level | Expected result | Notes |
206+
| --- | --- | --- | --- | --- |
207+
| TST-001 | Save Dropbox draft settings and reload the Settings page | UI | Provider selection, label, and validation message persist | `SettingsCloudStorageFlowTests` |
208+
| TST-002 | Disconnect Dropbox and clear persisted credentials | Component | Credentials are removed and subtitle resets to disconnected | `SettingsInteractionTests` |
209+
| TST-003 | Load the stored primary provider and auto-open its card | Component | Selected provider card renders open after state load | `SettingsInteractionTests` |
210+
211+
### Regression and analysis
212+
213+
- Regression suites to run:
214+
- `tests/PrompterLive.Core.Tests`
215+
- `tests/PrompterLive.App.Tests`
216+
- `tests/PrompterLive.App.UITests`
217+
- full solution tests
218+
- Static analysis:
219+
- repo build under `-warnaserror`
220+
- `dotnet format`
221+
- Coverage comparison:
222+
- solution coverage must stay at least at the previous baseline or improve
223+
224+
## Rollout and migration
225+
226+
- Migration steps:
227+
- ship the browser-storage and VFS wiring with the updated Settings UI
228+
- allow repository services to materialize runtime seeds and prior browser payloads into authoritative local browser JSON on first mutation
229+
- Backwards compatibility:
230+
- existing local library payloads are read and migrated instead of being dropped
231+
- settings still use browser-local persistence
232+
- Rollback:
233+
- revert the storage wiring, cloud UI, and repository changes together; partial rollback would break the storage boundary
234+
235+
## References
236+
237+
- [managedcode/Storage](https://github.com/managedcode/Storage)
238+
- [ManagedCode Storage docs](https://storage.managed-code.com/)
239+
- [docs/Architecture.md](/Users/ksemenenko/Developer/PrompterLive/docs/Architecture.md)
240+
241+
## Filing checklist
242+
243+
- [x] File saved under `docs/ADR/ADR-0001-browser-vfs-and-cloud-storage.md`.
244+
- [x] Status reflects the implemented state.
245+
- [x] Diagram section contains at least one Mermaid diagram.
246+
- [x] Testing methodology includes positive, negative, and edge flows plus pass criteria.
247+
- [x] New or updated automated tests exist for the changed behaviour.
248+
- [x] `docs/Architecture.md` updated for the new storage boundary.

0 commit comments

Comments
 (0)