Refactor text system: TextType/TextEntry replaces SpecialText - #1294
Merged
Conversation
…sting modules Revise the uploaded MediaOrganizer standalone design into a Bakabase-integrated architecture: reuse ThirdParty clients as metadata providers, Enhancer conversion chain for property writes, ResourceSourceLink for identity persistence, BTask hosting, and Comparison/Alias/SpecialText for matching. Adds a concept glossary, a user-defined organize-rule system with a path template DSL, StandardValue-backed type-safe domain fields, franchise/IP grouping for mixed libraries, plan/execute/rollback with resource-consistency coupling, and an M0-M4 roadmap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…ta-driven pipelines Per-layer components (signal extractors, providers, resolver policies) become user-selectable; pipelines are DB-stored configurations with read-only built-in presets that users can fork (copy-on-write). The Domain concept shrinks to a field vocabulary plus a routing/display label. Clarifies the Workflow module relationship: reuse its registry/descriptor/config-form patterns but not its runner; Workflow orchestrates around the organizer (triggers/activities) while the organizer keeps its own slot-based state machine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
Slot-by-slot comparison of the 8 built-in pipelines (signal chain, provider chain, resolver chain, field vocabulary), the thin detector slot, the four axes that actually differentiate them, and the component-reuse payoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…ver chain semantics Adds 5.2: signal.pattern engine with SignalRuleSet (typed named-group bindings) so users can define extraction rules; built-in plain-text signal components become seeded rule sets. Rewrites 6 into a two-phase matching model: search phase as a resolver chain of policies (Final/Augment/Pass, FirstHit vs FanOutUnion strategies, worked examples) and an enrichment phase with parallel EnrichAsync, identity sets, per-field merge policies and provenance. Extends the provider adapter contract accordingly and updates glossary, roadmap, and open questions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
Replace the monolithic HTML with docs/local-file-organization/: an overview (execution plan + progress tracker), a foundations doc (concepts, component model, type system, reuse map), and nine implementation modules ordered bottom-up from atomic capabilities (text rules, extractors, providers) through decision layers (resolution, enrichment) and disk-level capabilities (layout, execution) to top-level orchestration (pipelines, collections). Docs cross-reference by links instead of copying content; v1 comparison material is dropped. Renames the Signal concept to TextRule/Extractor/Clue (state Signaled -> Extracted) and adds a 'capabilities gained on completion' section with examples to every module. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…3-phase runner Drops the Certainty/Generate/Transform node-role taxonomy and the merge-as-chain-node design. Resolution now has a single component concept (ICandidateGenerator, with CandidateBasis expressing certainty per candidate) and a kernel runner with three fixed phases: generate (ordered, early-exit on deterministic candidates), consolidate (deterministic key-join always on, heuristic join configurable, conflict flagging, thin-field semantics), and decide (basis-aware banding). Documents the canonical MatchCandidate contract and syncs the pipeline examples in orchestration, enrichment, foundations and the overview. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…ailure-mode defenses MatchCandidate loses hardcoded Year/WorkType: canonical members are only what any work has (identity, titles, links) plus runner bookkeeping; all domain data moves into the typed FieldBag populated per provider field declarations. Heuristic consolidation and fuzzy scoring reference features via FeaturePredicate config reusing Comparison-module strategies, so the kernel contains no domain field names. Adds concrete Consolidate/Decide implementations, a failure-mode analysis with defenses (conservative defaults, visibility flags, IsSameWork / NotSameWork override assertions, code-lookup title cross-validation), and syncs provider/orchestration docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…, title role Restructures the consolidation section around the three-question separation (equivalence vs display value vs authoritative data), an exhaustive logic inventory showing the consolidator does not grow with domains, and pure-function/non-destructive replay semantics. Identity assertions (IsSameWork/NotSameWork) become a consolidator input with highest priority, the heuristic title gate compares alias-expanded normalized title sets, and a dedicated section documents title's role in both join paths including the granularity-mismatch signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…rent focus Marks docs/local-file-organization as pending (resolution-layer semantics not yet converged) and adds the file-cleaning workflow design: a user-defined chain built directly on the existing Workflow module (manual trigger carrying constants + child enumeration, filter activities, text-transform activities wrapping FileNameModifier operations and SpecialText-driven wrapped-text removal, and a save-name action with preview/apply/undo via FileRenameRecord). Extends SpecialText with user-defined custom types referenced as text sets by cleaning nodes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…xpand/variables mechanisms
Adds a 12-node worked example (HTML) covering multi-level entries and
cross-node linkage: the parent folder's cleaned title is captured as a
variable, inherited by expanded child file items, and combined with a
per-file episode capture in a template rename. Promotes the mechanisms
the example requires into the design doc: ExpandTo outcome with
expand.fs.children, item variables with capture/template activities and
{var} interpolation, common scope/requiredVars node options with
skip-not-drop semantics, and DeepestFirst apply ordering.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…O contracts
SpecialText extension upgraded to an elegant id-referenced design:
CustomTextType table + nullable CustomTypeId on SpecialText + a
discriminated TextSetRef (builtin:{enum} / custom:{id}) so renames
never break node references; seeded editable sets replace hardcoded
vocabularies; marked as implementation step one (the feature's only
schema change). The example doc gains per-node IN/OUT/cardinality/
variable-contract strips and a section on typing and conflict
isolation: kind namespacing, AcceptedInputItemTypes hard validation,
variable supply/demand as editor lint, and generic-node interop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…bility/binding spec The transform nodes read/write item.fs.entry's WorkingName and carry fs-specific options, so their kinds move from transform.text.* to transform.fs.* to match their declared I/O type. Generic text processing lives in a stateless TextOps service (the actual reusable component); workflow activities are bindings of that capability onto a concrete item type. Records the facet-based generalization path (AcceptedItemFacet validated via item type descriptors' ClrType) for when a second text-bearing item type appears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
… facet Accepts the review challenge that transform.fs.trim/capture/removeWrapped cannot answer 'which property does this operate on': the fs namespace has several string properties, so the systematic answer is a facet — items declare a single working text (FsEntryItem.WorkingText == WorkingName) and text nodes accept any item implementing ITextWorkpiece, validated by the editor via item type descriptors' ClrType. Text nodes return to the transform.text.* namespace with zero fs awareness; fs semantics reach them only through injected system variables (extension, parentName). Drops the scope option (mixed flows are expressed by chain ordering: directory segment -> save dir plan -> expand ChildrenOnly -> file segment), keeps fileNameOp deliberately fs-bound, and restructures the example to the new 13-node shape with facet contracts per node. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…ssthrough Clarifies how facet-typed text nodes chain with exact-typed fs nodes: the facet check is a capability predicate on the chain's current type, which never changes through facet nodes (mandatory Passthrough, no Fixed output), so item.fs.entry flows intact from capture into saveName. Runtime preservation is guaranteed by WithWorkingText returning the implementer's concrete type; capture only writes variables and keeps the item. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…fs.manualScan Surveys the Workflow module as a whole: current engine capabilities (typed chain validation, AdaptToNext, runner/rehydrator, runs), the actual node inventory (2 triggers, 5 item types, 5 activities - one subscription/downloader business line), six cross-cutting engine extensions (manual trigger with payload, ExpandTo, facet contracts, variables, two-phase runs, scheduled triggers) each with its first consumer, and the node roadmap by domain. Renames the cleaning trigger to fs.manualScan since manual filesystem scanning is not cleaning-specific. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…visions Converts file-cleaning-workflow and workflow-capability-map from markdown to styled HTML matching the example doc, cross-linked; the markdown sources are removed to keep a single source of truth. Applies the three confirmed revisions: cardinality becomes a static activity descriptor declaration for ExpandTo (editor filtering/marking/stats), the facet term is replaced by capability contract (AcceptedItemInterface) throughout, and variables move off item types into an engine-level per-item sidecar (ctx.Variables) with inheritance, persistence, interpolation and editor autocomplete/lint - simplifying ITextWorkpiece to WorkingText + WithWorkingText. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
Full design for the text-system rework, grounded in a code survey (~15 consumer files, four call patterns): custom text types become first-class (Custom=100 enum member, CustomTextType table with stable ids, CustomTypeId column, discriminated TextSetRef), and the mixed ISpecialTextService retires in favor of a vocabulary/operations split - ITextVocabularyService (storage, custom types, set resolution, seeds) and ITextOps (stateless clean/removeWrapped/trim/date-parse/interpolate, unit-testable without a DB) - housed in a new Bakabase.Modules.Text. Includes a member-by-member migration map, per-consumer migration table, idempotent seed ledger semantics, API/frontend changes, and a three-PR implementation split. Cross-linked from the cleaning design and the capability map's implementation order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
…e abstraction Replaces the enum-plus-custom-table split (and its builtin:/custom: discriminated TextSetRef) with one entity: TextType rows carry an optional WellKnown handle marking built-ins (undeletable, unrenamable, semantics bound at code consumption sites) and a Values/Pairs shape, with entries in TextEntry. References collapse to a single int typeId; GetTypes() serves pickers and the management page from one aggregated list, and custom Pairs types become usable as wrapper pairs directly. Existing SpecialText rows migrate losslessly via an app-level migrator (V220/V230 precedent) keeping EF migrations pure-schema; the SpecialText naming, enum, service and table retire entirely. Cleaning design and example synced to typeId references. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
Audited every claim in the design against current main: - Shape must be three-state, not two. Language also stores a pair (regex -> language label), and delimiter pairs (Wrapper) are semantically distinct from replacement mappings (Standardization/Language) - removeWrapped's wrappers parameter can only accept the former. The frontend already renders exactly these three shapes (Single/Wrapper/Value1ToValue2). - Drop the old table drop. App migrators run after EF schema migrations and hand-editing generated migrations is forbidden, so removing the model would let EF drop the table before the data migrator could read it - losing user rows on version-skipping upgrades. SpecialText's model and table now stay as a dormant relic. - Add the hidden ICustomDateTimeParser contract: SpecialTextService implements it and is wired via AddStandardValue, so the new TextOps must too or date parsing silently degrades. - Correct the blast radius: 12 real call sites, 43 files referencing the interface (25 downloaders pass it through), 167 mentioning the name. - Add four missed frontend couplings plus an API breakage (the display name editor indexes the response by enum value), the pretreatment endpoint, and the migrator's real version-threshold gating. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
First step of the text-system rework: builtin and user-defined text types will share one table and one id space, so anything referencing a type deals with a single integer. TextType carries an optional WellKnown handle (non-null marks a builtin, whose semantics are bound at consumption sites) and a Shape describing how entries use their two value slots — Values, DelimiterPair or MappingPair, since delimiter pairs and replacement mappings are not interchangeable. TextEntry keeps Value2 nullable for every shape so historical rows never lose data. Schema only: the existing SpecialText service and table keep running untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
Retires ISpecialTextService, whose single surface mixed storage CRUD, text operations and seeding, in favour of two services living in the new Bakabase.Modules.Text: - ITextVocabularyService owns types and entries. Builtin types are rows carrying a WellKnown handle, so GetTypes() returns one list for every picker; ResolveSet is the only way consumers read words, and it provisions a missing builtin on demand so a fresh database never throws where the old GetAll simply returned nothing. - ITextOps carries the operations, stateless and vocabulary-driven, and also serves as ICustomDateTimeParser — that binding is what feeds the user's date formats into StandardValue conversions, so it is covered by a test rather than left to compile-time luck. All 43 files touching the old interface move over: 12 real call sites map one-to-one onto the new methods, the rest is constructor plumbing through the downloader chain. Adds TextSystemMigrator, which copies legacy rows into the new tables after seeding builtins; it preserves Value2 even for shapes that ignore it (Volume ordinals) and skips entries already present, so a rerun after a crash cannot duplicate. The legacy SpecialText table and model deliberately stay in place — app migrators run after EF migrations, so dropping the table would destroy the source before the copy on a version-skipping upgrade. Tests split into TextVocabularyServiceTests and TextOpsTests, the latter needing no database for anything but the vocabulary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
The text page now renders one list of types straight from GetTypes, where builtins carry a badge and lose their rename/delete actions while user-defined types gain them. Entry editing keys off the type's shape rather than a fixed enum, so a custom mapping type gets the same two inputs as Standardization does. Follows the type through its remaining frontend couplings: the display-name template editor no longer indexes the response by an enum value (it finds the wrapper type by its well-known handle), and the enhancer target label and its StandardValue map move to WellKnownTextType. Localization keys move from SpecialTextType.* to WellKnownTextType.* and gain the shape and type-management strings. SDK regenerated: the specialText endpoints are gone, replaced by the text ones, and constants.ts picks up the two new enums. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
Three bugs were reported against the new text management page, all rooted in how builtin type rows came into existence. The page showed 2 types instead of 7 because TextSystemMigrator never ran: its gate was "2.4.0-beta", which sits below the installed 2.4.0-beta.82, and the gate asks whether the installed version is older. Raising it to 2.4.0-beta.99 fixes that instance, but the page should not depend on a one-shot migrator at all, so EnsureBuiltinTypes now runs from BakabaseHost.ExecuteCustomProgress on every startup — post-migration, before anything serves a request. Builtin types are defined in code, so their rows are an invariant, not seed data. "Add preset data" returned 500 because ResolveSet(WellKnownTextType) provisioned a missing builtin on read. Consumers share one scoped DbContext and several resolve concurrently, so two of them racing produced "A second operation was started on this context instance". Reads are now pure: a missing builtin resolves to an empty set, exactly as the old type-filtered query returned an empty list. Databases written by the lazily-provisioning build may already hold duplicate rows, so startup collapses them onto the oldest row and moves the entries across. That repair is why the new indexes are not unique — schema migrations run before it could clean up after them, so a unique index would fail startup on precisely the databases that need repairing. Seeding splits accordingly: EnsureBuiltinTypes creates type rows only, AddPrefabEntries (POST text/prefabs, renamed from text/seeds) tops up entries and stays user-triggered, so entries the user deleted are not resurrected on every launch. The three shapes were named for the storage model rather than the user; they are now "Single text" / "Bracket pair" / "Replacement" with a hint line each, shown both in the type picker and under custom types. Long server errors also overflowed the toast instead of wrapping it. Finishes the migration off ISpecialTextService, which was left half done. Bakabase.Abstractions no longer carries the unused domain model or SpecialTextType — the enum was still being emitted into the frontend's generated constants.ts as dead API. The db model moves to Bakabase.InsideWorld.Business as LegacySpecialText with a raw int type column; the table stays mapped because app migrators run after EF migrations, so dropping it would destroy the source before the copy runs on a version-skipping upgrade. Remaining "special text" naming is gone from the downloader chain, the dashboard stat and the frontend. Closes #1253
Running the pretreatment test with nothing entered round-trips to the server only to render an unchanged empty string. Trimmed, so whitespace alone does not enable it either.
Marks the extension complete across the three linked designs and adds an "implementation deviations" table to its own doc: seeding split into EnsureBuiltinTypes / AddPrefabEntries, reads made pure, non-unique indexes with a startup merge, the legacy model demoted out of abstractions, and the custom seed set deferred. The capability map's ordering is under review and may change; what it says today is the ordering as designed, not a decision.
The event bus writes a run's PayloadJson and the runner reads it back, so the two ends have to agree on serializer settings; they did, by separate copies happening to match. WorkflowJson.Options makes that agreement structural, and gives the manual-run path something to share rather than adding a fourth copy. No behaviour change — the settings are the ones all the copies already used.
Runs could only be created by an event. This adds the other entry point:
POST workflow/{id}/run, deliberately skipping both gates the event path
applies — the trigger filter (the user named this definition rather than
broadcasting an event) and the enabled flag (a workflow is most worth
trying by hand while it is switched off and being built).
Where the payload comes from is the trigger's business. The contract
gains BuildManualPayload(triggerFilterJson, argsJson) with a default that
reads it from what the user typed, so every event trigger becomes
replayable without writing code for it, and RequiresManualPayload so the
UI knows whether to ask. A trigger whose inputs live on the definition
overrides both — the shape fs.manualScan will take. The payload is built
before the run row is written, so a bad one is a failed request instead
of a run that dies the moment it starts.
The descriptor now carries the payload's field list, reflected the same
way item types already are, so the manual-run editor opens on a real
skeleton rather than an empty box.
Replaces RunNowAsync, which took an already-built payload and had no
callers.
The E1-E6 codenames were opaque outside the one table that defined them, so the map now opens with a lookup that gives each a name and a one-liner, and every later reference leads with the name. Records what a line-by-line read of the engine turned up, each with its effect on scheduling: - the engine had never executed a run — an injected provider was the resolving scope's, and every caller hands the runner to a BTask that outlives it. The two existing business lines had never worked. - type inference exists twice, in the service and as a hand-written mirror in the editor; the capability contract has to enter both. - two-phase running needs a second migration, so "the only schema change" in the cleaning design was wrong. - expansion is cheaper than estimated: step stats already carry independent in/out counts, which retires an open question. Ordering is now two candidates rather than one decision: the original by-engine-capability sequence, and a thin-vertical-slice one that pulls two-phase running forward on the grounds that a cleaning flow without apply is a toy. Four open questions are listed as open rather than answered.
The map jumped straight into a status inventory, so a reader had to reverse-engineer what the pieces are and how they connect. A new first section lays out the engine's concepts in the order they are used — what the user builds (definition, trigger config, activity chain), what code registers (trigger, item type, activity), how a run executes, and how the editor validates types along the chain — with each member marked as existing or as belonging to one of the six extensions. It closes with the mapping the design has needed all along: each one-line requirement of file cleaning against the concept it actually uses. That surfaces the point the prose never stated outright — only four things in cleaning require touching the engine, and everything else registers into slots that already exist, which is what lets a first vertical slice run without the capability contract or variables. Sections shift by one and the cross-references in the cleaning design follow; anchors verified in both directions.
…ip ci] The owner has positioned Workflow as the app's bottom-most automation layer: arbitrary internal events, user-composed chains, arbitrary actions. The map now states that positioning up front, with the mapping onto the three registry slots that makes per-event/per-action cost one class plus one hook. Backs it with a verified audit (three parallel code sweeps plus an independent critic that re-opened every citation; all engine claims held): - Five second-round findings on what app-wide generality requires of the engine: config parse failures silently become defaults ahead of destructive steps; one scope and DbContext span a whole run; runtime items are unchecked objects and the AI transform deserializes LLM text straight into typed input for side effects; events are published inline with no log and unmatched ones vanish; definition mutation has no in-flight guards, so disable and delete do not stop what is already queued or running. - A verified menu of ~25 candidate trigger surfaces (with four leverage points where one bridge covers a family) and ~25 candidate actions, each with the service method it would wrap and how item/config supply its inputs. - Open question 5: which hardening items gate destructive actions and when they land.
Ordering is thin vertical slices; the rename preview is one dual-mode component shared by the confirm panel and the run detail; the plan is one table whose rows live through pending/excluded/applied/undone; type inference converges on backend-shipped contract metadata before the capability contract lands; and the hardening hard-prerequisites become acceptance criteria of the apply batch rather than side work.
The first vertical slice of file cleaning (docs/file-cleaning-workflow.html, plan B batch 1): pick directories, chain name operations, and see the rename plan — with the disk untouched by construction. fs.manualScan is the first definition-configured trigger: no event fires it and a manual run needs nothing typed — BuildManualPayload builds the payload from the definition's own filter (roots, files/directories/both, depth, extension filter) and validates it in the request, so a missing root fails the click instead of producing a doomed run. Emits item.fs.entry (FsEntryItem), whose invariant carries the whole feature: transforms touch only WorkingName, and comparing it to OriginalName is how saveName knows what changed. transform.fs.fileNameOp wraps the existing FileNameModifier rule engine; its config IS the file-name-modifier page's operation model, and the editor reuses that page's OperationCard, so one rule engine has one config shape and one editor everywhere. action.fs.saveName is preview-only: each changed name becomes a FileRenameEntry row — plan, apply progress and undo record are one row moving through a status (owner decision 3), created now so batch 2 only adds transitions. Non-optional defenses per the design: Windows-rules sanitizing on every platform, duplicate-target detection within the plan and against the disk, path-length precheck; a conflict becomes a Conflict row rather than aborting the other 199. Writes go through the activity's own child scope, per the engine rule that the run-wide DbContext belongs to the runner. One endpoint serves the plan to both faces of RenamePlanPanel (read-only in this batch) — diff-colored rows via the FileNameModifier DiffHighlight, per-status counts, conflicts-only filter — reachable from fs runs in the runs drawer. Verified end to end against the live service: scan 3 entries, plan 2 Pending renames, Keep.txt planless, subdirectory untouched, disk unchanged. Suite at 611 passed / 0 failed including 7 new tests covering the chain validation, conflicts, sanitizing and payload rejection.
…rdening Apply & undo: - FileRenameEntryService: ApplyRun executes Pending rows deepest-first with per-row persistence (the row is the crash-consistency record); UndoRun replays Applied rows shallowest-first; SetExcluded toggles Pending/Excluded; DeleteByRunIds cleans up after definition deletion. - WorkflowController: excluded toggle, apply and undo endpoints; Delete now removes the runs' rename entries too. - RenamePlanPanel grows its interactive face (same component, two faces): exclude checkboxes, apply with confirm + count, undo, toast summaries. The runs drawer opens it interactive for succeeded runs only. Engine hardening (findings f5/f7/f9 + definition guards): - Malformed activity ConfigJson now throws WorkflowActivityConfigException and fails the run even under the Skip policy, instead of silently running the step on defaults. - The runner checks each item's CLR type against the step's accepted item types and refuses a broken chain at runtime. - The runner only executes Pending runs, so a stale BTask can't rerun a cancelled or finished run. - Disabling a definition cancels its Pending runs; deleting one refuses while a run is executing. - IsDestructive on activities + validation that a destructive step may not directly consume model-generated (AdaptToNext) input; exposed on the activity descriptor for the editor. Tested: 22 workflow tests incl. 8 new apply/undo/hardening tests; full suite 618 passed / 0 failed; live smoke against the running service (apply with an excluded row, disk verified, undo restores everything, re-include works).
…ence convergence Capability contracts (E3): - IWorkflowActivity.AcceptedItemInterface: an activity can accept any item whose CLR type implements a contract interface, instead of growing a per-type tag list. Contracts derive from the IWorkflowItemContract marker; a contract-accepting activity must be Passthrough (validated on save) and the runner checks the live instance against the contract at run time. - ITextWorkpiece: the text family's contract — one working text, replaced via WithWorkingText. FsEntryItem implements it (WorkingText ≡ WorkingName), so every text node applies to filesystem entries, and to any future text-bearing item type for free. Text nodes (design §3.4), all vocabulary-driven via ITextOps: - transform.text.removeWrapped — strip wrapped segments whose content matches a text set (wrappers must be a DelimiterPair type) - transform.text.removeTexts — strip bare occurrences of a set's entries - transform.text.trim — clean removal leftovers (whitespace/empty wrappers) A dangling or wrong-shaped text-type reference becomes WorkflowActivityConfigException: config staleness fails the run even under the Skip policy. Type-inference convergence (owner decision 4, B1): - Descriptors now ship the facts: acceptedItemInterface per activity, implementsInterfaces per item type (contract interfaces only). - The frontend chain walk and picker classification became generic set operations over that metadata — the accept rule evolves by changing backend metadata only, never frontend code. Frontend: config forms + summaries for the three nodes (shared TextType picker filtered by shape, match-mode select), "text" picker group, en/cn locales; docs status advanced (batches 1–3 done in the capability map). Tested: 5 new tests (end-to-end clean chain over disk, save-time rejection on non-text item types, config-staleness run failure, descriptor metadata, workpiece round-trip); 27 workflow tests green; full suite 623/0; live smoke: '[SubTeam] Movie SPONSOR (1080p).mkv' → planned 'Movie .mkv' via HTTP.
…ion (E2)
Variable bag (E4):
- Each item now travels with a chain-local bag of named string values, exposed
to activities as ctx.Variables. The bag rides BESIDE the item — item CLR
shapes never grow fields for chain state. On expansion each child inherits a
COPY of its parent's bag, so a season captured at the directory level reaches
every episode file without siblings bleeding into each other.
- transform.text.capture: named regex groups over the working text write the
bag (group name = variable name); onMiss chooses between continuing without
variables and failing the item.
- transform.text.template: rebuilds the working text from {var:name},
{var:name:pad(n)} and {originalText}; requiredVars is the missing-must-fail
gate. Domain system variables (IHasWorkflowSystemVariables) resolve after
the bag — FsEntryItem answers extension/originalName/parentName from its
original disk state.
- WorkflowVariableInterpolator is the single owner of the token grammar;
the editor mirrors it for autocomplete-style hints and soft linting.
Chain expansion (E2):
- WorkflowItemOutcome.ExpandTo(children) + Cardinality on activities; the
runner refuses expansion from a OneToOne activity as a declaration bug.
- transform.fs.expandChildren: a directory becomes its direct children
(target/extension filter, optional IncludeSelf); files pass through, so
mixed streams survive. Step stats' independent in/out counts show the 1→N.
Frontend: capture/template/expandChildren forms; config forms now receive the
upstream chain so template shows clickable "available variables" (captures +
fs system vars) and warns on tokens nothing provides.
Deviation noted in the capability map: bags are not persisted with the run —
nothing reads historical variables yet.
Tested: 5 new tests incl. the flagship dir-capture → expand → file-capture →
template scenario (two shows prove sibling-bag isolation) and the cardinality
guard; full suite 628/0; live smoke via HTTP: 1 directory → 2 files planned as
'Breaking Bad - S01E01.mkv' with the funnel showing 1→2.
…an + directory watch Scheduling: - IWorkflowScheduledTrigger: a trigger can also fire on the clock; the interval is per-definition data read from its own TriggerFilterJson, so one trigger kind serves every cadence. - WorkflowScheduler (predefined BTask, minutely): sweeps enabled definitions with a scheduled trigger, starts due ones through the exact manual-run path (identical validation and payload), never stacks a run behind a Pending/Running one, and records a failed start on the definition's LastError instead of killing the sweep. - fs.scheduledScan: the manual scan plus intervalMinutes (0 = manual-only). Directory watch (the FileMover-intake shape): - fs.watch trigger + WorkflowFsWatchService: FileSystemWatcher per watched root; an entry fires only after staying quiet for the definition's settle period, so files still being copied are never touched. Paths are pre-filtered per unique TriggerFilterJson and the payload carries that filter verbatim — Matches is a string comparison, definitions sharing an identical filter share one event, and no definition sees entries filtered for someone else's configuration. Renewed activity re-arms an entry. - The service exposes its refresh/note/tick pipeline publicly and takes the clock as a parameter, so tests drive it deterministically without timers. Frontend: trigger UIs for both kinds (scheduled scan reuses the manual scan form verbatim and appends the interval; watch gets roots/target/extensions/ settle), a shared FsRootsPicker extracted so the three fs forms cannot drift, runs-drawer rename-plan button now covers all fs trigger kinds; en/cn locales; docs mark E2–E6 and batches 1–5 complete. Tested: 6 new tests (scheduler due/skip/no-stack semantics, interval parsing, watch settle/dedupe/re-arm, per-definition extension filtering, end-to-end watch → run → plan); live smoke: a dropped file fired a watch run after its settle period, and the scheduler started the due definition on its minutely sweep.
A full workflow topic alongside the path-mark guide, matching its structure: - What Is It: the chain mental model (trigger → filter/transform/action), a minimal chain diagram next to what a run produces, the four part kinds with the editor's own colors, a three-step how-to, and the two-phase (plan → confirm → undo) safety callout. - Examples: seven complete recipes ordered from first-use to full automation — basic name cleaning with preview, vocabulary-driven de-noising, cross-level renaming with variables + expansion, watched folder auto-cleaning, nightly scheduled cleaning, subscription → keyword filter → download queue, and download-completion notification. Every recipe is drawn with a shared ChainDiagram component whose category colors mirror the real editor. - Concepts: ten glossary entries (trigger, activity, item type, type-checked chain, variables, plan/apply/undo, scheduled & watch triggers, manual run, error policy, run history). The workflow page header gets the "?" entry point opening this topic. All text ships in en + cn (158 keys each).
…skip ci] Design doc for replacing the modal workflow editor with a full-screen three-zone canvas editor: - §0 a working prototype embedded in the page itself: drag nodes from a grouped palette into the chain with magnetic insertion slots (the chain parts and the slot glows within the snap radius), drag chain nodes to reorder or off to a remove zone, click a node to configure it in a fixed right-hand inspector, and type-incompatible nodes dimmed with the reason. Click-vs-drag uses a 6px threshold so plain clicks stay clicks. - §1 diagnosis of the current modal editor's five pain points, each tied to the concrete file it comes from. - §2–§3 the three-zone layout and the exact interaction contract (90px snap radius, let-way animation, keyboard fallback). - §4 code mapping: chainWalk/activityFit/registries and every ConfigForm/FilterForm are reused verbatim; only the shell, the card and the picker are rewritten; dnd-kit stays, react-flow deliberately not introduced (the model is linear). - §5 a three-batch implementation plan and the open decisions. Prototype interactions verified end-to-end in Chromium (palette drop, magnetic slot, select, dim, remove) with zero console errors.
Replaces the modal editor with the three-zone canvas editor designed in docs/workflow-editor-redesign.html (owner decisions: route page, direct replacement, entry points included): - /workflows/editor route: node palette (grouped, searchable, incompatible nodes dimmed with the reason) · horizontal chain canvas (trigger node, typed edge pills, per-node config summaries and status dots) · fixed right-hand inspector hosting the exact same FilterForm/ConfigForm components, so the chain never jumps while configuring. - Magnetic drag (useChainDrag): a 6px click-vs-drag threshold, slots that snap open with a glow inside a 90px radius, validity-gated magnetism for palette drops (invalid positions simply never open; single-accept nodes still enter via the auto-inserted AI bridge), drag-off-to-remove, and a click/keyboard path (Enter/arrows/Delete) so dragging is never the only way. Ghost follows the pointer imperatively; only slot/remove state goes through React. - Entry points (file-cleaning design §6, previously missing): a "new from template: file cleaning" button on the workflow page, and an "upgrade to a cleaning workflow" button on the File Name Modifier page that carries the current operation set into a prefilled fileNameOp node via a one-shot sessionStorage seed. - Old modal editor (WorkflowEditor/ActivityCard/ActivityPicker) removed; chainWalk/activityFit/registries and every form component are reused verbatim. ItemTypePill gains a horizontal variant for canvas edges. - Fixes surfaced by the live drive: the item.fs.entry type had no localized display name (edge pills showed the raw i18n key). Verified end-to-end in Chromium against the running app: template creation, palette drag with slot glow, click-to-append, inspector editing enabling save, save round-trip (create → ?id= URL → run/history controls), list integration, trigger-locked note on edit, drag-to-remove with empty-state hint, and the File Name Modifier hand-off; tsc at 342 (two below the prior baseline — deleting the old editor removed its errors).
Ending a drag nulled session.current BEFORE cleanupGhost() read the ghost back through that same ref, so the cloned card was never removed — every completed or cancelled drag left a frozen copy at the drop position, which read as "the node got stuck". The session now ends through one endSession path that removes the ghost from the captured session object, and the teardown also covers pointercancel (native drag / touch scroll stealing the pointer) and unmount mid-drag; ghosts carry data-drag-ghost so tests can assert none remain. Verified in the running app: drop on a slot (inserted, 0 ghosts), drop in empty canvas (cancelled, 0 ghosts), four rapid cancelled drags (no accumulation), and a chain-node move — all clean, no console errors.
Owner feedback on the canvas editor: the chain hugged the top-left and the canvas could neither pan nor zoom. - The chain now renders on a transformed world layer inside an overflow-hidden viewport. On open, fit-view centers it vertically and horizontally; fit never shrinks below 75% so node labels stay readable — a chain wider than the view anchors to its start and pans instead. - Pan: drag empty canvas space (interactive elements excluded, 3px threshold so clicks pass through), or plain wheel / trackpad two-finger. - Zoom: ctrl/⌘ + wheel (= trackpad pinch) zooms to the cursor, 35%–200%; a corner toolbar adds −/+, a percent button that resets to 100%, and a fit button. The dot grid follows the view via background size/position. - Pan and zoom write through a ref and apply styles imperatively — a pointermove stream must not re-render the editor; only the percent label goes through state. Magnetic slot hit-testing already works in screen coordinates, so dragging is unchanged at any zoom; the drag ghost now matches the source's on-screen size instead of its layout size. - The remove zone is pinned to the canvas (absolute) rather than the old scroll container. Verified in the running app: exact vertical centering on open, drag-pan and wheel-pan, cursor-anchored zoom with toolbar sync, fit/reset buttons, palette drop onto a glowing slot while zoomed out, node click still selecting, zero leftover ghosts, no console errors.
Make the entire block surface the drag source and drop the top-right drag-handle button, guarded by a 6px click-vs-drag threshold so plain clicks and jitter stay inert. Rework drag/resize into pointer sessions with a single exit path — pointerup commits, pointercancel and unmount abort back to the committed layout — replacing the permanently registered window listeners that were re-subscribed on every preview render and read state through closures. Previews are deduped by grid placement so pointermove no longer re-settles and re-renders while the pointer stays in the same cell. Closes #1293 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
Split enum imports that are only used in type positions into `import type` (consistent-type-imports errors in the text-ops shared form and the canvas inspector panel), and add the `common.action.rename` key the text page uses to both locales, which the locale key-resolution test flagged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H
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
This PR refactors the text management system by replacing the legacy
SpecialTextmodel with a newTextType/TextEntryarchitecture. The change introduces a modular text processing framework with support for text operations (capture, template, trim, remove), vocabulary management, and integration with the workflow system.Key Changes
Core Architecture
SpecialTextmodel andISpecialTextServiceinterfaceTextTypeandTextEntrymodels inBakabase.Modules.TextITextOpsinterface for text operation abstractions (capture, template, trim, remove-wrapped, remove-texts)ITextVocabularyServicefor managing text vocabularies and entriesTextVocabularyServiceimplementation with CRUD operations and indexingWorkflow Integration
TextCaptureActivity,TextTemplateActivity,TextTrimActivity,TextRemoveWrappedActivity,TextRemoveTextsActivity)FsFileNameOpActivity,FsSaveNameActivity,FsExpandChildrenActivity)FsWatchTrigger,FsManualScanTrigger,FsScheduledScanTrigger)WorkflowFsWatchServicefor file system monitoringWorkflowSchedulerTaskfor scheduled workflow executionIWorkflowActivityinterface with new properties for activity metadata and cardinality supportWorkflowRunnerwith improved variable interpolation and execution contextFile Rename Management
FileRenameEntrymodel for tracking file rename operationsFileRenameEntryServicefor managing rename entriesFileNameSanitizerutility for safe file namingTextType,TextEntry, andFileRenameEntrytablesUI Components
CanvasEditor,CanvasNode,InspectorPanel,NodePalette)ActivityCardandActivityPickercomponentsWorkflowEditorcomponentDocumentation
docs/local-file-organization/:foundations.md- Core concepts and type systemmodules/01-text-processing.md- Text processing rulesmodules/02-extractors.md- Entry extractionmodules/03-providers.md- Metadata providersmodules/04-resolution.md- Identity resolutionmodules/05-enrichment.md- Field enrichmentmodules/06-layout.md- Path templates and organization rulesmodules/07-execution.md- Execution and rollbackmodules/08-orchestration.md- Pipeline orchestrationmodules/09-collections.md- Collection aggregationworkflow-capability-map.html,workflow-editor-redesign.html,file-cleaning-workflow.html,specialtext-extension.html)Testing
TextOpsTests- Text operation functionalityTextVocabularyServiceTests- Vocabulary service operationsWorkflowAutomationTests- Workflow automation scenariosWorkflowVariablesTests- Variable interpolationWorkflowFsApplyUndoTests- File systemhttps://claude.ai/code/session_017XD61DUNaB5iVEevJrA26H