feat: storage provider abstraction + FTP provider#168
Merged
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lter), not TargetPath Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, TLS hardening Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aming Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a provider <select> at the top of the import dialog driven by IStorageProviderRegistry, and render the field set from the active provider's capabilities. GitHub (SupportsBranches) keeps the existing owner/repo/branch/subpath fields and GitHubImportService flow unchanged; credentials providers (FTP) get host/port/username/password/useTls/base-path fields, authenticate via Registry.Active.Auth, persist via StorageCredentialStore, and import via StorageImportService. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add StoragePendingChangesService exposing SupportsPullRequests (from the active provider's capabilities) and SubmitAsync, which routes ProviderFileChange sets to the active provider's WriteChangesAsync. Gate CreatePullRequestDialog by capability: GitHub (SupportsPullRequests) keeps the existing fork->branch->commit->PR flow and "Create Pull Request" button untouched; non-PR providers (FTP) show an "Upload changes" confirmation view and button that calls SubmitAsync. Note: FTP change-set tracking from VFS edits is not yet wired (no tracker populates changes for FTP-imported files), so the upload list is empty today; the capability gating, labelling, and service plumbing are in place for when that tracking lands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Throw InvalidOperationException from StoragePendingChangesService.SubmitAsync when the active provider supports pull requests, turning a future mis-call into a clear contract violation instead of WriteChangesAsync's NotSupportedException. Marshal the bare StateHasChanged() calls in OnProviderChanged and StartFtpImport through InvokeAsync for consistency with the other awaited Dispatcher calls. Document that FTP credential fields are intentionally not cleared on Close so they re-prefill from the credential store next time, unlike the GitHub fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Storage Providers section to CLAUDE.md (IStorageProvider abstraction, capability-driven branch/PR handling, GitHub + FTP implementations, the Blazor registry/UI), and correct the plan's misleading subpath-mapping comment to reflect the implemented SubPath (remote filter) vs TargetPath (VFS destination). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces a storage-provider abstraction so the VFS can load from / write to multiple backends, migrates the existing GitHub support onto it with no behavior change, and adds a read+write FTP provider with a capability-driven Blazor UI. Built per the design spec & plan under
docs/superpowers/(merged via #167).Atypical.VirtualFileSystem.Providers.Abstractions(new):IStorageProvider,IStorageProviderAuth,ProviderCapabilities, and neutral records. Branch/PR/fork are capability flags +CommitContextfields (not base-interface methods), so providers that can't do PRs (FTP) simply ignore them.GitHubStorageProvider/GitHubProviderAuth/adapters wrap the existing Octokit loader & write service — the original 71 GitHub tests still pass (regression gate).Atypical.VirtualFileSystem.Ftp(new):FtpStorageProviderover FluentFTP behind anIFtpConnectionseam (TDD against an in-memory fake) — recursive import with size filtering, read, and write (per-file overwrite/delete with partial-success results), leak-safe connect/disconnect, TLS opt-in.IStorageProviderRegistryselects the active provider; a capability-driven import dialog (provider picker) and capability-gated "submit changes" (PR for GitHub, upload for FTP). GitHub import/PR flows preserved unchanged; FTP credentials persisted encrypted viaProtectedLocalStorage.Each phase was implemented test-first and passed spec + code-quality review.
Follow-ups (out of scope, tracked)
GitHubImportDialog→StorageImportDialog.Test plan
dotnet build Atypical.VirtualFileSystem.sln -c Release— 0 errorsdotnet test— 970 test runs pass on net9.0 + net10.0 (399 Core + 76 GitHub + 5 Abstractions + 5 FTP, ×2 TFMs)🤖 Generated with Claude Code