docs(176): fix plan test-project path + parse.ts self-review nits; add SDD ledger

This commit is contained in:
2026-07-18 02:41:50 +02:00
parent 8c7af0e202
commit aa4127fcef
@@ -30,7 +30,7 @@
- `ErsatzTV.Application/Search/Queries/GetSearchFieldCatalogHandler.cs` — returns the curated static catalog.
- `ErsatzTV.Application/Search/SearchFieldCatalog.cs` — the curated static list (product-owned, cross-references `LuceneSearchIndex`).
- `ErsatzTV/Controllers/Api/SearchController.cs`**modify**: add the `GET /api/v1/search/fields` action.
- `ErsatzTV.Application.Tests/Search/GetSearchFieldCatalogHandlerTests.cs` — NUnit test (new; place under the existing `ErsatzTV.Application.Tests` project — confirm the exact test project name with `ls *.Tests`).
- `ErsatzTV.Tests/Application/Search/GetSearchFieldCatalogHandlerTests.cs` — NUnit test (new; Application handler tests live under `ErsatzTV.Tests/Application/<Domain>/`, namespace `ErsatzTV.Tests.Application.<Domain>`, run via `dotnet test ErsatzTV.Tests`).
**SPA (new — `web/src/builder/rules/`):**
- `types.ts``FieldType`, `Operator`, `Rule`, `Group`, `Match`, `isGroup`.
@@ -56,15 +56,14 @@
- Create: `ErsatzTV.Application/Search/SearchFieldCatalog.cs`
- Create: `ErsatzTV.Application/Search/Queries/GetSearchFieldCatalog.cs`
- Create: `ErsatzTV.Application/Search/Queries/GetSearchFieldCatalogHandler.cs`
- Test: `ErsatzTV.Application.Tests/Search/GetSearchFieldCatalogHandlerTests.cs`
- Test: `ErsatzTV.Tests/Application/Search/GetSearchFieldCatalogHandlerTests.cs`
**Interfaces:**
- Produces: `record SearchFieldResponseModel(string Name, string Label, string Type, string Group, string[] Values)`; `record GetSearchFieldCatalog : IRequest<List<SearchFieldResponseModel>>`; `SearchFieldCatalog.Fields` (static `List<SearchFieldResponseModel>`).
- [ ] **Step 1: Confirm the test project name**
- [ ] **Step 1: (resolved) Test location**
Run: `ls /Users/timothy/ersatztv/.claude/worktrees/176-rule-builder | grep Tests`
Expected: a directory like `ErsatzTV.Application.Tests` (use its real name for the test path below).
Application handler tests live in `ErsatzTV.Tests/Application/<Domain>/` with namespace `ErsatzTV.Tests.Application.<Domain>`, run via `dotnet test ErsatzTV.Tests`. The new test goes in `ErsatzTV.Tests/Application/Search/`. This repo uses **explicit `using`s** (no ImplicitUsings) — include the `System.*` usings shown in Step 5. Exemplar: `ErsatzTV.Tests/Application/Channels/PreviewAutoTuneChannelsHandlerTests.cs`.
- [ ] **Step 2: Write the response model**
@@ -176,15 +175,19 @@ public class GetSearchFieldCatalogHandler : IRequestHandler<GetSearchFieldCatalo
- [ ] **Step 5: Write the failing NUnit test**
`ErsatzTV.Application.Tests/Search/GetSearchFieldCatalogHandlerTests.cs` (match the real namespace of sibling tests):
`ErsatzTV.Tests/Application/Search/GetSearchFieldCatalogHandlerTests.cs`:
```csharp
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Application.Search.Queries;
using ErsatzTV.Core.Api.Search;
using NUnit.Framework;
using Shouldly;
namespace ErsatzTV.Application.Tests.Search;
namespace ErsatzTV.Tests.Application.Search;
[TestFixture]
public class GetSearchFieldCatalogHandlerTests
@@ -242,13 +245,13 @@ public class GetSearchFieldCatalogHandlerTests
- [ ] **Step 6: Run the test to verify it fails, then passes after building**
Run: `cd /Users/timothy/ersatztv/.claude/worktrees/176-rule-builder && dotnet test ErsatzTV.Application.Tests --filter GetSearchFieldCatalogHandlerTests`
Run: `cd /Users/timothy/ersatztv/.claude/worktrees/176-rule-builder && dotnet test ErsatzTV.Tests --filter GetSearchFieldCatalogHandlerTests`
Expected: fails to compile first if any type is missing, then PASS once Steps 24 are in. **Grep the build for `error CS` before trusting a `--no-build` result.**
- [ ] **Step 7: Commit**
```bash
git add ErsatzTV.Core/Api/Search ErsatzTV.Application/Search ErsatzTV.Application.Tests/Search
git add ErsatzTV.Core/Api/Search ErsatzTV.Application/Search ErsatzTV.Tests/Application/Search
git -c core.hooksPath=/dev/null commit -m "feat(176): search field catalog query + curated list"
```
@@ -641,15 +644,14 @@ function parseAtom(atom: string, fieldTypes: Record<string, FieldType>): Rule |
if (negate) return null; // NOT only valid with quoted forms above
// Wildcards (text only)
// Wildcards (text only): *v* → contains, v* → startsWith
if (raw.startsWith('*') && raw.endsWith('*') && raw.length >= 2) {
if (type !== 'text') return null;
return mk(field, 'contains', unescape(raw.slice(1, -1)));
return mk(field, 'contains', unescapeWild(raw.slice(1, -1)));
}
if (raw.endsWith('*') && !raw.includes('*', 0)) { /* handled below */ }
if (/^[^*]+\*$/.test(raw)) {
if (type !== 'text') return null;
return mk(field, 'startsWith', unescape(raw.slice(0, -1)));
return mk(field, 'startsWith', unescapeWild(raw.slice(0, -1)));
}
// Bare token → numeric eq only (reject fuzzy ~, boosts ^, etc.)
@@ -658,7 +660,7 @@ function parseAtom(atom: string, fieldTypes: Record<string, FieldType>): Rule |
return null;
}
function unescape(value: string): string {
function unescapeWild(value: string): string {
return value.replace(/\\([+\-!(){}[\]^"~:\\/])/g, '$1');
}
@@ -697,7 +699,7 @@ export function parse(input: string, fieldTypes: Record<string, FieldType>): Gro
- [ ] **Step 4: Run the parser test to verify it passes**
Run: `cd web && npx vitest run src/builder/rules/parse.test.ts`
Expected: PASS. If the `startsWith` branch misbehaves, note the intent: a value that ends in a single trailing `*` and contains no other `*` is `startsWith`; the regex `/^[^*]+\*$/` captures exactly that. Remove the dead `if (raw.endsWith('*') && ...)` line if the linter flags it.
Expected: PASS. If the `startsWith` branch misbehaves, note the intent: a value that ends in a single trailing `*` and contains no other `*` is `startsWith`; the regex `/^[^*]+\*$/` captures exactly that.
- [ ] **Step 5: Commit**
@@ -1218,7 +1220,7 @@ Run:
```bash
cd /Users/timothy/ersatztv/.claude/worktrees/176-rule-builder
dotnet build ErsatzTV.sln 2>&1 | grep -E "error|Build succeeded"
dotnet test ErsatzTV.Application.Tests --filter GetSearchFieldCatalogHandlerTests
dotnet test ErsatzTV.Tests --filter GetSearchFieldCatalogHandlerTests
cd web && npx vitest run && npx tsc -b --pretty false && npm run lint && npm run check:api
```
Expected: `Build succeeded`, all tests PASS, `check:api` clean (no uncommitted generated diff).