Resource subsystem redesign: named roots, sidecar pairing, structured ops#701
Merged
Conversation
Reworks ResourceKey to support optional named roots in the form "root:path" with an implicit default root "project". Introduces DefaultRoot, Root, Path and FullKey properties; improves parsing and validation (TryParse/IsValidKey), preserves round-trip and equality semantics, and updates hashing/comparison. Renames API surface from RootFolder to ProjectFolder and updates all usages in explorer, menus, drag/drop, viewmodels, file tools, registry, and tests to reflect the new project-root semantics. Adds/adjusts unit tests to cover root parsing, equality, Combine/GetParent behavior, and descendant logic.
Introduce IResourceRootHandler and ResourceRootCapabilities to allow pluggable, named resource roots. Expand IResourceRegistry with RegisterRootHandler, RootHandlers, IsResolvable, and a root-scoped GetAllFileResources overload. Implement root handler plumbing in ResourceRegistry (auto-registers a ProjectRootHandler when ProjectFolderPath is set, resolves via handlers, and returns clear errors for unregistered roots). Revise PathValidator to validate against a specified root/backing location, scope its verified-folder cache per-root, and update its API accordingly. Add ProjectRootHandler and update tests to cover per-root caching, handler registration, resolvability, and scoped enumeration.
Introduce a new hidden .celbridge layout (temp, logs, trash) and support virtual resource roots backed by those folders. Add ProjectConstants entries for the new layout and update PythonService and ResourceOperationService to use the .celbridge paths. Extend IResourceRootHandler with GetResourceKey and implement a shared ResourceRootHandlerBase plus TempRootHandler and LogsRootHandler. Update ResourceRegistry to dispatch GetResourceKey to the longest-prefix matching root. Rework ResourceMonitor to create one FileSystemWatcher per watched root, route events with the originating handler, and only debounce project-root updates. ResourceService now prepares the .celbridge folders, clears trash on load, registers temp/logs handlers, and migrates/removes legacy trash. Add tests for registry dispatch and virtual root handlers, and include helper TryClearFolderContents. Misc: small formatting and icon lookup whitespace fixes.
Update resource key docs to describe named roots (project:, temp:, logs:), canonical output rules, and stricter validation (lowercase multi-char roots). Propagate canonical resource-key reporting into runtime: switch error messages to echo the resourceKey, emit canonical per-entry keys in ReadMany, and adjust package/spreadsheet tooling to report keys consistently. Add tests ensuring non-project roots are reported with their root prefix (e.g. temp:...) while project-root keys are reported as bare paths. This makes diagnostics unambiguous across multiple roots and fixes related regressions.
Introduce a new IResourceFileSystem chokepoint and implementation, replacing the previous IResourceFileWriter. The new interface exposes read/write/open stream APIs plus structural ops (Move/Copy/Delete) and sidecar-aware result types (SidecarOutcome, MoveResult, CopyResult, DeleteResult). Added ResourceFileSystem service implementation and comprehensive ResourceFileSystemTests; removed the old ResourceFileWriter and its tests. Updated DI registration and IWorkspaceService to expose the new file-system layer, migrated callers across the workspace (commands, viewmodels, tools, tests) to use the new API (including async writes and atomic staging behavior). Added a staging folder constant (CelbridgeStagingFsFolder) used for atomic temp-file staging. Also adjusted PackageTools to use the file-system layer for atomic writes and made related async fixes.
Introduce IPythonInstaller (Foundation) and move FileHashHelper to Celbridge.Utilities (core) while removing the old Workspace copy. Convert PythonInstaller from a static helper to an injectable class implementing IPythonInstaller, add logging, and register it in DI. Update PythonService to depend on IPythonInstaller, use the shared FileHashHelper, and add a stable install-state hash (ComputeInstallStateHash) to the offline/online fingerprinting logic with additional debug logging and disk-state snapshots.
Reorganize Celbridge.Utilities by relocating helper classes into a centralized Helpers directory. Files moved (no code changes): FileHashHelper.cs, GlobHelper.cs, LineEndingHelper.cs (from root) and PathHelper.cs (from Services) to Source/Core/Celbridge.Utilities/Helpers to improve project structure and discoverability.
Introduce sidecar/frontmatter support and a workspace metadata index. Adds SidecarHelper (TOML frontmatter parsing/composing using Tomlyn), SidecarInfo/SidecarStatus and SidecarReport types, and IResourceMetaData interface plus a ResourceMetaData implementation that builds a reference graph and manages frontmatter operations. Wire the service into DI and expose it via IWorkspaceService; update FileResource to track its paired sidecar. Extend IResourceRegistry with GetSidecarParent and GetSidecarReport. Add many unit tests for parsing, classification, tracking and metadata scanning, and update tests to supply an ILogger to ResourceRegistry. Also add Tomlyn package reference.
Introduce structured result types and error details for resource operations: add CopyCommandResult, DeleteCommandResult, DeleteResourceResult, DeleteBatchOutcome, DeleteResourceOutcome, DeleteReferencePolicy, SkippedReferencer and ReferencerSkipReason; extend MoveResult with skipped referencers. Update ICopyResourceCommand/IDeleteResourceCommand and IResourceOperationService method return types to surface rich outcomes (CopyResult/MoveResult payloads). Update explorer tools to emit compact "ok" for clean cases and structured JSON for partial failures or skipped referencers, add reference-policy parsing for deletes, and implement silent duplicate behavior using a new ResourceNameHelper. Update tests to exercise move/copy/delete cascades, referencer rewrites, and duplicate naming. Documentation/guides updated to describe canonical quoted references, policies, and tool responses.
Rename MonitoredResource* messages to Resource* and update all registrations/handlers and tests to use the new message types. Inject IMessengerService into ResourceFileSystem (and use it in ResourceOperationService) to synchronously broadcast ResourceDeletedMessage for moved/deleted keys (including captured descendant file keys) so subscribers can update immediately (watcher events remain idempotent). Improve folder-aware referencer handling in DeleteResourceCommand (expand folder targets, avoid blocking internal deletions) and aggregate per-resource failure Results in CopyResourceCommand so callers receive detailed errors. Update tests to supply a messenger mock and use the new message types. Add helper to enumerate descendant keys and several explanatory comments.
Add Result.Fail(Result) to create a failure that re-uses an inner Result's error chain, and update callers to use it rather than creating new wrapper failures and calling WithErrors. Many command and filesystem paths (CommandService, ResourceOperationService, ResourceOperations, ResourceFileSystem, SidecarHelper, DocumentStateProvider, PrintPropertyCommand, etc.) now return Result.Fail(inner) to preserve original messages. Also make some FS methods genuinely async and simplify Task.FromResult returns, adjust move tool logic and docs to emit a compact "ok" only for no-side-effect moves and return a structured status payload for other outcomes, and tweak a few variable/notification names in resource commands.
Update explorer_delete.md to state that resourceResults contains one entry per input resource (e.g. one entry for a folder delete, not per descendant) and that per-descendant external-reference detail is exposed in referencers. Update explorer_move.md to note that the cascade will rewrite any project-referencing file (including test fixtures, scripts, or docs that quote "project:<source>") and so such files may be updated in-place, which can surprise authors. These clarifications reduce confusion about what is rewritten and where reference details appear.
Introduce first-class support for .cel sidecar metadata: add MetaDataTools MCP toolset (metadata_get/list/set/remove, metadata_add_tag/remove_tag, metadata_find, metadata_check_project) with JSON parsing and index wait semantics. Extend file info snapshots and the file_get_info result to expose paired sidecar key and a SidecarStatus. Add IProjectCheckCommand and ProjectCheckReport for project-wide consistency checks, plus a ResourceMetaDataCache service and ProjectConstants for the .celbridge/cache/metadata.json cache file. Add documentation guides for the metadata namespace and each tool, and include unit tests covering metadata cache persistence and project-check behavior; wire up related resource/service code to surface the new functionality.
Introduce a new data surface for .cel sidecar handling: adds on-demand IResourceScanner, ISidecarService, and many ICommand interfaces for reading/writing fields, tags and named blocks (add/find/get/set/remove/read/write). Replace the older metadata-focused guides and tools with the data namespace and move tool implementations accordingly. Update IWorkspaceService to expose ResourceScanner and SidecarService (remove the prior metadata persistence surface), add ProjectCheckError and console strings for reporting findings, and flip feature flags for note-editor and mcp-tools. Also remove legacy metadata cache/constants and adjust tests, workspace commands, and service implementations to match the new data/sidecar API.
Clarifies that data_get_info returns an empty { "fields": {}, "blocks": [] } both when the sidecar is missing and when the parent resource doesn't exist, because the tool only inspects the sidecar file. Advises using file_get_info first to verify the parent exists; retains the note about clear errors for broken sidecars and alternatives (file_read, data_check_project).
Make ResourceKey.ToString() always return the canonical "root:path" form (including the explicit "project:" prefix for the default root) and update call sites, docs and tests accordingly. Adjust tools to expose empty selection/document as empty string (signal none) while keeping canonical serialisation for non-empty keys. Update WebView screenshot/resolver and URL code to use the Path portion for filesystem/URL operations. Normalize sidecar TOML frontmatter to LF line endings to avoid mixed CRLF/LF. Minor cleanup in ResourceFileSystem GetSidecarKey return style. Tests and guides updated to reflect the new canonical form and behavior.
Use the resource key's Path (bare path) where callers expect or construct filesystem paths, and keep the canonical ToString() only when the root should be visible. - ResourcePickerItem: DisplayText now uses resourceKey.Path for default project-rooted resources and falls back to resourceKey.ToString() for non-default roots so the root remains visible when needed. - ContributionDialogHandler: Pass result.Value.Path to GetRelativePathFromResourceKey (that method expects the bare path) instead of ToString(), avoiding the canonical "project:" prefix. - CopyPathMenuOption: Use resourceKey.Path when combining with ProjectFolderPath to form a filesystem path (ToString() emits a root prefix that doesn't belong in a filesystem path). This clarifies UI display and prevents accidental inclusion of the root prefix in paths used for file operations or relative-path computation.
Introduce a central ResourceScanRules with an ExcludedExtensions set (currently ".md") and IsExcludedExtension helper. ResourceScanner now skips files with excluded extensions, meaning quoted "project:" literals inside .md files are treated as documentation (not machine-tracked) so cascade rewrites and broken-reference checks ignore them. Update docs (resource_keys.md) to explain the exclusion and consequences. Update tests to use .txt fixtures where scanning is expected, and add tests that verify Markdown references are excluded and that sidecar files (e.g. .md.cel) are still scanned because the rule matches the final extension.
Replace the previous exclusion-based scan rules with an explicit allowlist of scannable extensions embedded in ResourceScanner, remove the standalone ResourceScanRules, and drop the text/binary sniffer dependency. Update docs to describe the allowlist approach and clarify scanning semantics (sidecars, what is/aren't scanned). Adjust tests to use allowlisted data files (e.g. .json) and update expectations accordingly. Also simplify CopyResourceCommand by removing IProjectService usage and changing CopySingleResourceAsync to resolve source/destination paths via IResourceRegistry.ResolveResourcePath (with failure handling), ensuring prefixes like project:/temp: are handled correctly instead of combining with a project folder path.
Enforce disk-canonical case for project resource keys and improve error propagation across tools. - ResourceRegistry: add strict case enforcement for the project root (reject wrong-case keys that resolve to existing disk paths), with a diagnostic message naming the canonical key; allow put/create flows when the resource doesn't exist. Add helper EnsureProjectKeyCaseMatchesDisk and related logic. - ResourceFileSystem: skip referencer rewrites when the referencer file is DOS read-only (log a warning and record a skipped referencer) and add IsReferencerReadOnly helper to check file attributes. - Tools: propagate ResolveResourcePath failure messages (resolveResult.FirstErrorMessage) rather than returning generic messages in multiple File/Package/Spreadsheet tools. - Tests: add unit tests for case-rejection and non-existent resource behavior; update integration tests (Python) to expect canonical "project:..." resource keys from tool responses and adapt test fixtures to use scanner-allowlisted extensions (.json) where needed. - Docs: update guides to state that resource keys are case-sensitive on all platforms and that malformed sidecar files block data_* mutations (with guidance to repair via file_write). These changes prevent inconsistent behavior on case-insensitive filesystems (Windows), make errors clearer to callers, and ensure referencer rewrites respect read-only files.
Add support for exact filename matches and multi-part extension suffixes for document editors. Introduce SupportedFilenames on IDocumentEditorFactory and DocumentEditorFactoryBase, update CanHandleResource to prefer filename matches and to check multi-part extensions. Extend DocumentEditorRegistry to index filename-to-factories (case-insensitive), handle null collection properties, try filename matches before extension matches, and walk longest-to-shortest extension suffixes. Add CelFileClassification and CelFileClassifier to classify .cel sidecars (Standalone, Sidecar, Orphan) using the resource and editor registries. Persist per-file editor choice via sidecar when using "Open With" and have DocumentsService consult a sidecar's editor preference before falling back to normal resolution. Add ProjectFileFactory and ModManifestFactory and register them in the module (plus project references). Add resource strings and update docs. Include extensive unit tests for multi-part resolution and .cel classification.
Major API and implementation reshuffle across Foundation and Workspace layers to standardize resource, .cel sidecar, and document-editor handling. Key changes: - Introduces cross-root and project tree abstractions: IRootHandlerRegistry, IProjectTreeBuilder and FolderItem + EnumerateFolderAsync on IResourceFileSystem. - Reworks sidecar model and services: ISidecarService signature changed (ReadAsync now accepts resource), GetInfoResult gains HasSidecar, ISidecarPairingService added, and many typed sidecar operations added (SetField/RemoveField/AddTag/RemoveTag/WriteBlock/RemoveBlock). - Resource registry and transfer helpers moved/adjusted: IResourceRegistry ProjectFolderPath is now read-only and InitializeProjectRoot added; destination-resolution and context-folder helpers moved into IResourceTransferService. - Document editor API updates: IDocumentEditorFactory adds IsPlaceholder and simplifies CanHandleResource; IDocumentEditorRegistry and IDocumentsService gained new methods for richer factory lookup and preferred-editor resolution; DocumentConstants adds CodeEditorId and SidecarEditorFieldName; DocumentEditorId implicit-from-string removed. - File/extension semantics adjusted: WebView extension canonicalized to ".webview.cel"; various .cel classification logic consolidated (CelFileClassification removed in favour of pairing service). - Manifest and resources: Package.appxmanifest version bumped to 0.3.0.0 and resource string keys for package/document contribution renamed/added. - Adds numerous concrete implementations, helpers, tests, and workspace wiring to exercise the new APIs. This is a breaking/internal API refactor intended to centralize and clarify resource resolution, sidecar ownership, and editor-selection logic; callers should adapt to the new method signatures and types.
Add a test-only NLog.config that routes all logging to a Null target to prevent incidental service logs from surfacing on stdout/stderr (avoids GitHub Actions auto-annotating expected error output). Update the test project file to copy the NLog.config to output, taking precedence over the shared logging config. Improve ResourceRegistryTests by capturing UpdateResourceRegistry results into a variable and passing updateResult.FirstErrorMessage into assertions for clearer failure diagnostics; add a Platform("Win") attribute to the case-sensitivity test with an explanatory reason.
Add an immutable EditorId to IDocumentView and DocumentView and require factories to stamp views with their EditorId. Update HtmlViewer, WebView and CustomDocumentViewFactory (and tests) to set EditorId; DocumentsService now fails if a created view has an empty EditorId. Introduce DocumentConstants.TextBoxFallbackEditorId and stamp it on the last-resort TextBoxDocumentView. Refactor DocumentViewFactory to resolve resource paths earlier, simplify sidecar/extension/priority creation helpers to return constructed views (null for fall-through), and improve error/logging and return semantics. Update DocumentsPanel and DocumentsPanelViewModel to use the view's EditorId for tab labels and adjust tests accordingly.
Attach a GotFocus handler to the acquired WebView and detach it when the view is removed to avoid duplicate subscriptions and leaks. Add WebView_GotFocus implementation that sends a DocumentViewFocusedMessage(FileResource) via the messenger service so the app is notified when this document's WebView receives focus.
Define two new file icon entries (_cog_medium-green and _gears_medium-green) with FontAwesome glyphs and medium-green color, update file-icon mappings to use _gears_medium-green for "cel" and _cog_medium-green for "celbridge" (replacing the previous database icon), and add a code-editor mapping for ".cel" to use the Ruby language mode for syntax highlighting.
Replace direct filesystem/path resolution with the resource file system chokepoint and async/streamed I/O across the codebase. Added ResourceInfo record and GetInfoAsync(ResourceKey) (replacing ExistsAsync) so callers can probe NotFound/File/Folder plus size/modified-time in one roundtrip. ISpreadsheetReader now operates on Stream inputs (stateless reader) and spreadsheet tools open workbook streams via ResolveWorkbookResourceAsync/OpenWorkbookStreamAsync. File tools now use WorkspaceService.ResourceFileSystem for GetInfoAsync, ReadAllTextAsync, ReadAllBytesAsync, OpenReadAsync and avoid System.IO.File/DirectoryInfo calls; a ReadFileLinesStreamedAsync helper was added to stream lines safely. Several interfaces were made async or renamed: IPackageService.RegisterPackagesAsync, IResourceTransferService.CreateResourceTransferAsync, IInspectorFactory.CreateResourceInspectorAsync, and IDialogFactory.CreateResourcePickerDialog signature updated. Tests and many command/tool classes updated accordingly to route I/O through the containment chokepoint and support streaming/async patterns.
Add IResourceClassifier and ResourceClassificationResult and replace the older sidecar-pairing API. Rename SidecarStatus->CelFileStatus and SidecarInfo->SidecarLink, introduce FileKind on IFileResource, and change SidecarReport->CelFileReport/GetCelFileReport. Delete ISidecarPairingService. Update migration logic to only convert pre-0.3.0 .webview files to .webview.cel (leave .toml manifests alone). Update module manifests and document editor filenames to use package.toml / *.document.toml, and adjust tests, helpers, and usages to match the new names and behavior.
Rename the chokepoint API from IResourceFileSystem to IFileStorage and update related types and members across the codebase. ResourceInfoKind -> StorageItemKind and ResourceInfo -> StorageItemInfo were renamed; IWorkspaceService.ResourceFileSystem was changed to FileStorage. All callers (tools, commands, services, UI viewmodels, tests and docs) were updated to use the new interface and type names, and the foundation interface file was renamed accordingly. CLAUDE.md updated to reflect the new API name. This aligns naming with broader "storage" semantics and centralizes file access behind the FileStorage chokepoint.
Rename the ReferenceLiteralRules module to ResourceReferenceParser and update all call sites. FileStorage and ResourceScanner now reference ResourceReferenceParser.ReferenceMarker, IsNonKeyBoundary, and TryParseReferenceAt. Comments and XML docs were rewritten for clarity; parsing behavior and algorithms are unchanged (only naming and wording refinements).
Introduce IFileStorage.ComputeHashAsync and implement it in FileStorage to return a SHA256 hex/base64 digest via FileHashHelper. Remove the FileAccessHelper class and replace its usage with direct FileStorage.GetInfoAsync/Open/ComputeHashAsync calls. Update DocumentLayoutStore, DocumentsService, DocumentViewModel, ConsolePanelViewModel and tests to use the new ComputeHashAsync or GetInfoAsync checks, switch to string-based hash tracking and comparisons, and remove direct SHA256 byte-hashing and related using directives. Also clarify FileHashHelper documentation to distinguish hashing of external file paths vs. resource-backed reads.
Refactor FileStorage to centralize the bounded-retry policy into a generic RunWithRetryAsync<T> and replace ad-hoc read/write retry loops. ReadAllBytesAsync, ReadAllTextAsync and OpenReadAsync now use the shared runner; OpenReadAsync returns a FileStream wrapped by the retry chokepoint. Added IsTransientReadIOException to avoid retrying FileNotFoundException/DirectoryNotFoundException and tightened logging/error messages via an operationLabel. Updated SpreadsheetTools to open a read-only stream from the storage chokepoint instead of loading workbook bytes into a MemoryStream. Minor API doc/comment wording tweaks.
Remove exact-filename editor registrations from the document editor API and implementation, and update classifier/tests to stop relying on filename-only matches. IDocumentEditorRegistry no longer exposes IsFilenameSupported; DocumentEditorRegistry drops its IsFilenameSupported method. ResourceClassifier no longer checks filename registrations when deciding standalone .cel forms and only considers multi-part extension suffixes. Tests were adjusted to remove the filename-only test and update mock expectations accordingly.
Replace temporary file-based zip creation with an in-memory MemoryStream. The ZipArchive is written into a MemoryStream (leaveOpen:true) and the resulting bytes are used directly for CreateFileAsync, removing temp file creation, reading and cleanup. Error handling and result population remain, and archive size is determined from storage probe or the in-memory byte length as before.
Make PathValidator instance-scoped to a single root/backing location (constructor now takes rootName and backingLocation) and move verified-folder cache inside it. ResourceRootHandlerBase now creates its own PathValidator and exposes InvalidatePathCache; IResourceRootHandler gained InvalidatePathCache and IRootHandlerRegistry.InvalidatePathCache delegates to each registered handler. Remove the shared PathValidator from RootHandlerRegistry and update ResourceService/ResourceRegistry and tests to stop wiring a shared validator. Adjusted tests and handler constructors to match the new API and removed the cross-root cache-scoping test.
Introduce FileStorage, ResourceRegistry and WorkspaceSettings properties on SearchService and replace direct WorkspaceService lookups with these properties. Inline and remove the ReplaceInFileAsync and ReplaceMatchAsync helpers by performing read/replace/write directly (with cancellation checks and error handling) to reduce duplication and simplify flow. Use ResourceRegistry for path resolution and FileStorage for file IO throughout. Also remove a redundant XML summary comment from SidecarService.cs.
Remove the standalone FileFilter and move its filtering logic into SearchService as an internal ShouldSearchFileAsync method (size and extension checks, using ITextBinarySniffer). Update SearchService to depend on ITextBinarySniffer, add constants/sets for max file size and excluded metadata extensions, and adjust search flow to call the new method and sniff text content. Rename and update tests (FileFilterTests -> SearchServiceFilterTests) to exercise SearchService.ShouldSearchFileAsync and wire a real FileStorage through the workspace chokepoint. Add InternalsVisibleTo for the test assembly in the Celbridge.Search csproj so tests can access the internal method.
Hoist workspace-scoped service lookups into class-level properties (FileStorage, ResourceRegistry, ResourceOperationService, ResourceTransferService, ResourceScanner, SidecarService) and replace per-method/local lookups. Update CopyResourceCommand and DeleteResourceCommand to use these properties for Resolve/Transfer/Operation/Scanner calls and batch Begin/Commit. Inline previous helper checks (folder probe and sidecar existence) using FileStorage/SidecarService and remove the now-unused private helper methods. This simplifies code, reduces repeated workspace resolution, and aligns with workspace-scoped DI usage.
Introduce a workspace-scoped ITrashService and register TrashService; add CreateFolderAsync(ResourceKey) to IFileStorage and implement it in FileStorage (idempotent, creates missing parents). Convert IResourceOperationService to ResourceKey-based APIs (CreateFile/CreateFolder/Copy/Move/Delete/Transfer) and add ImportExternalFileAsync/ImportExternalFolderAsync for imports. Update commands/helpers to call the new chokepoint signatures and use the file-storage probe for validation; remove the FileSystemHelper and move the move-with-retry logic into FileStorage. Add tests for folder creation and comprehensive TrashService behavior. Note: this is an API-breaking change to resource operation signatures and behavior (path strings -> ResourceKey).
Return a disposable IBatchScope from BeginBatch so batches commit on Dispose (RAII-style); update callers (copy/delete/transfer/unarchive) to use using scopes. Add BatchScope implementation and discard empty batches while committing partial batches. Rename PathValidator to RootPathResolver, expose GetResourceKey (path->key) and OS-aware comparers, and wire it into ResourceRootHandlerBase. Refactor FileStorage: rename MoveWithRetry to RetryTransientIOAsync, await retries for file/dir ops, add TryMapDescendantKey and broadcast ResourceKeyChanged/ResourceDeleted messages for moves, and centralize transient-IO retry usage. Simplify ResourceOperationService by removing messenger dependency, passing ILogger into ImportExternalOperation, and removing internal broadcast logic (batching now handles commit). Update TrashService to accept IMessengerService and send ResourceDeleted messages on successful soft-deletes. Add and adjust tests (ResourceOperationServiceTests added; PathValidatorTests renamed/expanded to RootPathResolverTests) and minor comment/text updates. These changes improve batch semantics, two-way path/key resolution, retry handling, and resource messaging consistency.
Replace costly SHA256 hashing with lightweight size+mtime tracking for saved files: DocumentViewModel now caches file size and modified-time, uses GetInfoAsync for pre-/post-write checks, and updates IsFileChangedExternallyAsync/UpdateFileTrackingInfoAsync accordingly. This prevents unnecessary hashing on watcher events and detects post-write interleaves by size mismatch. ContributionDocumentView now coalesces concurrent external-reload requests (single in-flight reload with an optional follow-up) and syncs view-model tracking after reloads to short-circuit duplicate watcher events. Tests updated: renamed a test to reflect size-based post-write detection, made the injected test override public, and added a test ensuring watcher self-events after our own save do not cause ReloadRequested.
Introduce a ReloadHint enum and a TTL-backed ReloadHintStore to let commands register how the next watcher-driven reload should treat editor view state (preserve current view or let disk win). Expose RegisterReloadHint/ConsumeReloadHint on DocumentsService and wire consumption in ContributionDocumentView to pass a preserveViewState flag through the host NotifyExternalChange RPC. Update the spreadsheet module to register DiskWinsOnViewState when set_active_view runs and to honor preserveViewState in the JS restore path. Add unit tests for ReloadHintStore behavior (round-trip, overwrite, TTL expiry). Default behavior is PreserveViewState when no fresh hint is present.
If an already-open document is reopened without an explicit address, preserve its current section instead of moving it to the active section. Added a null-address check to set sectionIndex = existingSection.SectionIndex so tabs aren't unexpectedly yanked from the user's section; tabs are still moved when a different section is explicitly requested.
Switch PackageService to accept an injected PackageRegistry instead of constructing it internally. ServiceConfiguration now registers PackageRegistry as a transient service. Adjust PackageService by removing several constructor parameters and unused usings, and assigning the injected registry. Update tests (FileTypeProviderTests, PackageRegistryTests) to create a PackageRegistry and pass it to PackageService. These changes improve DI alignment and testability by centralizing registry construction.
Introduce a dedicated temp:downloads folder and refactor temporary path handling across the codebase: - Add CelbridgeTempDownloadsFolder to ProjectConstants and update docs to clarify that temp: is wiped on workspace load (use project: for persistent data). - WebViewDocumentView: ensure temp:downloads exists, stage downloads under temp:downloads, resolve a unique project:downloads destination, import to project:downloads, and delete the staging copy after import or on interruption; add an in-file GetUniquePath helper and include Projects namespace. - ProjectTemplateService: stop using PathHelper; use ApplicationData.Current.TemporaryFolder for staging new projects. - Remove PathHelper.cs (its functionality was inlined/migrated where needed). - WriteFileCommand: simplify line-ending handling by extracting ResolveTargetSeparatorAsync and use LineEndingHelper consistently. - ResourceService: clear temp: contents on workspace load to enforce the wipe-on-load contract. - Update Celbridge.UserInterface.csproj to reference Celbridge.Utilities and tidy an EmbeddedResource element. These changes centralize download staging under temp:, avoid leftover staging files, and make temp: lifecycle explicit.
Extract resource/storage enums and DTOs into FileStorageTypes.cs and update IFileStorage/IResourceOperationService/IGetFileInfoCommand/IResourceRegistry to use the new types and cleaner parameter names (dest). Rename project downloads constant to DownloadsFolder and update WebView download paths. Add FileStorageInternals.RunWithRetryAsync and refactor FileStorage to use it; introduce SidecarCascade and ReferenceRewriter and add sidecar fence-line validation in SidecarHelper. Optimize RootPathResolver path normalization and adjust document layout restore to stop persisting editor ids. Harden migration step (recover partial .webview -> .webview.cel conversions) and add/adjust unit tests accordingly.
Introduce LegacyConstants to hold legacy (user-visible) "celbridge/" folder names and move legacy-specific names out of ProjectConstants. ProjectConstants is cleaned up to reflect the new hidden ".celbridge/" layout (rename Celbridge* constants to shorter names, add PythonFolder). Update callers across projects and tests to use LegacyConstants for legacy paths and ProjectConstants for the new .celbridge subfolders, and adjust resource/cleanup, staging, logs, trash and Python-related code accordingly to use the correct constants. This separates legacy locations from the current layout while retaining compatibility for migration and cleanup code.
Replace the CelFileReport type with SidecarReport and update related APIs and callers. IResourceClassifier.ClassifyResources now returns SidecarReport; ResourceRegistry stores and exposes SidecarReport from UpdateResourceRegistry. The classifier no longer returns a sidecar-to-parent lookup and GetSidecarParent was removed. Root handler access was moved to IRootHandlerRegistry (exposed via IResourceService.RootHandlerRegistry) and callers/tests were updated accordingly. Other cleanups: delete unused IFileOperationService, simplify SidecarService return values, tighten ResourceOperationService return handling, adjust FileTools to use the root handler registry, improve ResourceMonitor logging and file-ignore checks, and update tests to match the new APIs.
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.
Here's a PR summary for the
resource-redesignbranch:Title
Resource subsystem redesign: named roots, sidecar pairing, and structured operationsSummary
This branch is a ground-up rewrite of the Resource subsystem. 48 commits, 405 files changed, +20,470 / −5,369. The main themes:
1. Named resource roots (
project:,temp:,logs:)Every
ResourceKeynow carries an explicit root, and resolution flows through a pluggableIRootHandlerRegistryofIResourceRootHandlerimplementations (ProjectRootHandler,TempRootHandler,LogsRootHandler). Per-rootPathValidatorenforces scoped path safety;RootPathResolveris the single reparse-point chokepoint that every operation routes through.ResourceKey.ToString()is canonicalized to include its root.2.
FileStoragereplacesResourceFileWriterA single workspace-scoped
IFileStorageis now the IO chokepoint for reading, writing, hashing, and probing resources. Supporting machinery split intoSidecarCascade(cascade rules for moves/copies/deletes) andReferenceRewriter(textual reference rewrites). Retry and read-only handling are shared withTrashServicevia common helpers.3. Sidecar (
.cel) file systemNew
ISidecarServiceplus aResourceClassifierthat runs during everyUpdateResourceRegistrypass and produces aSidecarReportofHealthy/Broken/Orphansidecars.FileKindis now first-class onFileResource(PlainData,Sidecar,Standalone,Orphan,InvalidSidecar). Standalone forms (foo.webview.cel,foo.note.cel) are recognised via the document editor registry rather than hard-coded.4. Structured resource operations
IResourceOperationServicenow returns typedCopyResult/MoveResult/DeleteResourceOutcomerecords instead of bareResult. Move/copy cascades report per-resource success and failure; entity-cascade rollback is wired intoMoveOperation. Failure taxonomies between delete outcomes and reference-rewrite skip reasons are unified.5. New project diagnostics
ProjectCheckCommand+ProjectCheckReportersurface registry health (orphan sidecars, broken.cel.celfiles, reference targets) as a user-visible report.6. Document editor cleanup
Filename-based editor routing is gone; matching is now extension-based with multi-part extension support (e.g.
.webview.cel).EditorIdis tracked and enforced on document views. Sidecar editors win on layout restore.7. Resource monitor
Allowlist-based scanner with per-root cache invalidation. Files are tracked by size + mtime to avoid spurious reloads; reload events are coalesced. Reload hints flow through a new
ReloadHintStoreso views know whether a reload was triggered externally or by the editor itself.8. Trash
New
ITrashServiceprovides recycle-bin-style staging for deleted resources with purge support.9. Search service
Pulled the existing
FileFilterintoSearchService; search now resolves roots through the registry rather than walking raw paths.10. Tests
Substantial new coverage:
FileStorageTests(~830 lines),ResourceClassifierTests,ProjectTreeBuilderTests,DataCheckProjectTests,MultiPartExtensionResolutionTests,MigrationStep_0_3_0_Tests,ResourceOperationServiceTests.1252 / 1252 passing.