feat: add complete backward-compatible post search #285

Merged
erik merged 1 commit from feat/task-ba500871-post-find into main 2026-09-24 21:08:54 -05:00
Owner

Summary

Adds post find <query> [--type article|link|note] [--status draft|published|all] [--tag <slug>] [--json] with complete, backward-compatible search across articles, links, and notes.

  • Matches trimmed case-insensitive literal substrings in title, slug, stored content, excerpt, and destination URL, including Unicode and literal special characters.
  • Adds optional API q with full-field filtering before pagination, preserving existing filters, published-only/all-types defaults, ordering, authentication, response summaries, and pagination metadata. Missing/blank API queries retain the original listing path.
  • CLI traverses every page until empty, locally checks matches for older sites that ignore q, and fetches details where summaries omit necessary search fields. Short pages do not truncate search; repeated/non-advancing pages, missing detail content, and failed requests fail instead of printing partial results.
  • Human output includes IDs/slugs/types/titles/status. JSON aggregates original matching summaries in { "data": [...] } without misleading single-page metadata. Existing post commands/output stay unchanged; no migration or mandatory site upgrade.
  • Successful API logs include effective query/type/tag/publication status, pagination, and returned count, excluding credentials/unrelated parameters/post contents.
  • Updates CLI help/README, command/API/OpenAPI specs, and reviewed/manual existing-site upgrade instructions.

Task: task-ba500871

Verification

  • ./scripts/pre-pr.sh: all checks passed, including formatting, CSS consistency, lint, types, 314 CLI tests and 432 template-site tests.
  • npm run build: all workspaces passed.
  • 63 new focused tests cover updated/older sites, every type/search field, Unicode/literal semantics, combined/default filters, nulls, content-only later-page matches, pagination beyond 100 records, JSON/human output, usage/auth/API/network errors, repeated pages, read-only behavior, and safe logging. Existing post commands remain covered; OpenAPI assertions extended.
  • Live smoke test against localhost:3000 passed title/slug/content/excerpt/URL searches, Unicode/literal matching, default published-only and explicit draft/all filters, tags/types, empty results, human/JSON output, and unchanged records before/after searches.
  • Live CLI returned all 106 matching fixtures across pages. Captured actual logs show q=post-find-smoke, publicationStatus=all, tag=post-find-smoke, with offset/result pairs 0/100, 100/6, 106/0.
  • A content-only draft-note match absent from the default first 50 unfiltered records was found with API q=hidden-note-needle&status=draft&limit=1&offset=0; offset 1 returned no matches. CLI detail fetching found the same post. Blank API query matched the unchanged baseline.

Local evidence: /tmp/slug-post-find-pre-pr.log, /tmp/slug-post-find-build.log, /tmp/slug-post-find-live.log, /tmp/slug-post-find-live-api.log. Local fixtures are identifiable by tag/slug prefix post-find-smoke and remain available for manual checks. Fixtures were created through the local API without publish/update federation operations.

Compatibility and tradeoffs

Search uses JavaScript lowercasing to preserve Unicode/literal semantics rather than SQLite LIKE/FTS. Nonblank API queries scan eligible rows in memory before paging; only matching-page records are hydrated. Older sites may require many CLI detail reads. Offset pagination is not a snapshot; avoid concurrent collection changes while searching. These tradeoffs are documented. Resource endpoints remain generated-site code; package-only upgrades and advisory slug upgrade do not rewrite them. Publication of the CLI remains a separate step.

## Summary Adds `post find <query> [--type article|link|note] [--status draft|published|all] [--tag <slug>] [--json]` with complete, backward-compatible search across articles, links, and notes. - Matches trimmed case-insensitive literal substrings in title, slug, stored content, excerpt, and destination URL, including Unicode and literal special characters. - Adds optional API `q` with full-field filtering before pagination, preserving existing filters, published-only/all-types defaults, ordering, authentication, response summaries, and pagination metadata. Missing/blank API queries retain the original listing path. - CLI traverses every page until empty, locally checks matches for older sites that ignore `q`, and fetches details where summaries omit necessary search fields. Short pages do not truncate search; repeated/non-advancing pages, missing detail content, and failed requests fail instead of printing partial results. - Human output includes IDs/slugs/types/titles/status. JSON aggregates original matching summaries in `{ "data": [...] }` without misleading single-page metadata. Existing post commands/output stay unchanged; no migration or mandatory site upgrade. - Successful API logs include effective query/type/tag/publication status, pagination, and returned count, excluding credentials/unrelated parameters/post contents. - Updates CLI help/README, command/API/OpenAPI specs, and reviewed/manual existing-site upgrade instructions. Task: task-ba500871 ## Verification - `./scripts/pre-pr.sh`: all checks passed, including formatting, CSS consistency, lint, types, 314 CLI tests and 432 template-site tests. - `npm run build`: all workspaces passed. - 63 new focused tests cover updated/older sites, every type/search field, Unicode/literal semantics, combined/default filters, nulls, content-only later-page matches, pagination beyond 100 records, JSON/human output, usage/auth/API/network errors, repeated pages, read-only behavior, and safe logging. Existing post commands remain covered; OpenAPI assertions extended. - Live smoke test against localhost:3000 passed title/slug/content/excerpt/URL searches, Unicode/literal matching, default published-only and explicit draft/all filters, tags/types, empty results, human/JSON output, and unchanged records before/after searches. - Live CLI returned all 106 matching fixtures across pages. Captured actual logs show `q=post-find-smoke`, `publicationStatus=all`, `tag=post-find-smoke`, with offset/result pairs `0/100`, `100/6`, `106/0`. - A content-only draft-note match absent from the default first 50 unfiltered records was found with API `q=hidden-note-needle&status=draft&limit=1&offset=0`; offset 1 returned no matches. CLI detail fetching found the same post. Blank API query matched the unchanged baseline. Local evidence: `/tmp/slug-post-find-pre-pr.log`, `/tmp/slug-post-find-build.log`, `/tmp/slug-post-find-live.log`, `/tmp/slug-post-find-live-api.log`. Local fixtures are identifiable by tag/slug prefix `post-find-smoke` and remain available for manual checks. Fixtures were created through the local API without publish/update federation operations. ## Compatibility and tradeoffs Search uses JavaScript lowercasing to preserve Unicode/literal semantics rather than SQLite LIKE/FTS. Nonblank API queries scan eligible rows in memory before paging; only matching-page records are hydrated. Older sites may require many CLI detail reads. Offset pagination is not a snapshot; avoid concurrent collection changes while searching. These tradeoffs are documented. Resource endpoints remain generated-site code; package-only upgrades and advisory `slug upgrade` do not rewrite them. Publication of the CLI remains a separate step.
feat: find posts without losing paginated or content-only matches
All checks were successful
CI / build-lint-test (pull_request) Successful in 2m6s
1b8783e1c9
Task: #task-ba500871
Author
Owner

Review update

  • PR: #285 — open; task task-ba500871.
  • Reviewed head: 1b8783e1c991b78e956c4811d89ca0a7b217b625, confirmed by fj pr view 285 commits.
  • Result: approved. All 10 current acceptance criteria are met; 0 blockers, 0 warnings.
  • Independent review inspected the actual fj pr view 285 diff, narrowly relevant existing helpers, regression tests, and supplied local verification/live evidence. No implementation changes or broad checks were performed.

Acceptance criteria

  1. met — Find all post types with existing defaults. runPostsFindCommand registers the new command; its request defaults to status=published and omits type. API filtering retains the established publication predicate. CLI/API fixtures cover articles, links, and notes; live default draft-content search returns zero and explicit draft search finds the note.
  2. met — Full-field literal/Unicode matching and absent fields. CLI and API compare title, slug, content, excerpt, and URL with JavaScript toLowerCase().includes(), matching existing sources/account/contact conventions. Queries are trimmed before searching, optional/null fields are safely skipped, and omitted summary content triggers detail reads when needed. Tests and live evidence cover mixed-case École, content-only matches, excerpts, URLs with query characters, and literal %_ [ rather than SQL wildcards or regular expressions.
  3. met — Combined filters preserve semantics. Existing parameterized type/status/tag predicates remain intact and combine with text matching. CLI reuses post-flag/query helpers, including first repeated tag handling; API retains tag normalization. Automated cases exercise draft/all/default status and type/tag intersections, including empty intersections; live logs show effective combined filters.
  4. met — Optional API search before pagination without regressions. listPosts filters eligible complete rows before slice(offset, offset + limit) for nonblank queries. The absent/blank branch keeps SQL LIMIT/OFFSET; both branches retain timestamp/ID ordering and hydrate only the returned page into existing content-free summaries. Route response metadata and bearer authentication remain unchanged. Tests compare blank-query responses to baseline, check later matches beyond 105 filler rows, paging, validation, authentication, and unchanged records. No migrations or endpoint/package relocation are introduced.
  5. met — Complete updated/older-site traversal and failure propagation. findPosts sends encoded q, requests up to 100 records, advances by actual returned length, and terminates only on an empty page, not a short page. Local verification handles older servers ignoring q; unresolved summaries fetch encoded-slug details. Repeated records and missing required detail content raise errors; request failures propagate before any results are printed. Tests cover both API versions, shortened pages, a content-only match after 100 records, repeated pages, failed details, and a later-page network failure. Live evidence confirms 106 aggregate matches and actual offset/count pairs 0/100, 100/6, 106/0.
  6. met — Useful output, aggregate JSON, no-match success, read-only behavior. Human output includes ID, slug, type, title or (untitled), and publication status for follow-up post show. JSON returns original matching summaries in { "data": [...] } without misleading single-page metadata. Empty results produce No posts found. or an empty data array with success. Requests are GET-only, detail content is not added to output summaries, and automated/live before-and-after evidence shows unchanged posts.
  7. met — Usage validation and distinct operational failures. Query and flag parsing precede site/client creation and requests; blank/missing queries, extra arguments, invalid type/status, unsupported flags, and missing values are tested as usage errors with zero requests. Standard configured-site resolution and the existing HTTP client's authentication/API/network error classification are reused. Tests explicitly cover detail 401/403/404/422/500 and later-page network failure rather than treating them as no matches.
  8. met — Regression coverage. The diff adds 40 CLI and 23 API focused test cases, plus OpenAPI assertions. Coverage includes types/fields, nulls, Unicode/literals, defaults and combined filters, multiple/empty results, older/updated APIs, short/multiple pages and later content-only matches, human/JSON output, invalid input, read-only behavior, authentication and request failures. Existing command paths are unchanged except find registration/help. Supplied full-suite evidence records 314 CLI and 432 site tests passing, alongside the package suites.
  9. met — Safe successful-request logs and live verification. The posts route explicitly allowlists trimmed query, effective publication status, supplied type/normalized tag, pagination, result count, method/path and HTTP status. It does not copy arbitrary query parameters, credentials, or post bodies. Tests assert the exact context, absence of credential/unrelated-parameter/content values, zero-result logging, and no success logs for auth/validation failures. Sanitized live API logs contain actual combined-filter pagination/count records and the content-only draft query with limit 1 returning one result then zero; that match was absent from the first 50 unfiltered records.
  10. met — Documentation and customization-safe optional upgrade. CLI help and cli/README.md, post command/API specifications, OpenAPI parameters, and docs/package-updates.md cover fields, literal semantics, defaults, combined filters, examples, full pagination/detail reads, aggregate JSON/no-match behavior, compatibility, and errors. Upgrade guidance explicitly requires reviewed per-file changes preserving customizations, adapted tests, build/test checks, redeployment, and live verification; package-only updates and advisory slug upgrade do not rewrite copied endpoints.

Quality and security assessment

  • Scope is limited to post search, its tests, logging, and documentation; existing commands and auth enforcement are preserved. The small CLI search module keeps traversal/matching separate from command/output handling and reuses established client/parser conventions.
  • Query text is not interpolated into SQL or used as a regex; existing SQL filters remain parameterized. Detail slugs and query parameters are encoded. Search does not perform mutations or publish/federation operations, and logs avoid response content and authorization material.
  • Documented tradeoffs were assessed, not treated as defects: nonblank API search scans eligible rows in memory on each request for consistent Unicode/literal behavior; older sites can require many sequential detail requests; offset pagination is not a snapshot during concurrent edits. The implementation is correct for a stable collection, detects repeated records, and the documentation explicitly advises against concurrent collection changes. No unrequested indexed-search, snapshot, or endpoint-migration requirement is imposed.
  • Blocking issues: none.
  • Warnings requiring correction or waiver: none.

Verification and CI state

  • /tmp/slug-post-find-pre-pr.log: full pre-PR checks passed, including 314 CLI and 432 site tests.
  • /tmp/slug-post-find-build.log: all-workspace build passed.
  • /tmp/slug-post-find-live.log and /tmp/slug-post-find-live-api.log: reviewed sanitized live searches, actual pagination/count logs, and reported unchanged before/after records.
  • CI: success — supplied verified Forgejo build-lint-test run #477 at reviewed head 1b8783e1c991b78e956c4811d89ca0a7b217b625. This worker's fj pr status 285 could not deserialize Forgejo's relative job URL; CI state relies on the explicitly supplied verified run/head evidence, not a successful status refresh. No CI rerun was requested or performed.

Final verdict

approved — criteria 10/10 met, 0 missing, 0 blockers, 0 warnings. Review approval is not merge authorization.

Next: Wait for explicit human merge approval. Do not merge or close the task; CLI publication remains separate.

### Review update - PR: https://forge.caradoc.com/erik/slugkit/pulls/285 — open; task `task-ba500871`. - Reviewed head: `1b8783e1c991b78e956c4811d89ca0a7b217b625`, confirmed by `fj pr view 285 commits`. - Result: **approved**. All 10 current acceptance criteria are met; 0 blockers, 0 warnings. - Independent review inspected the actual `fj pr view 285 diff`, narrowly relevant existing helpers, regression tests, and supplied local verification/live evidence. No implementation changes or broad checks were performed. ### Acceptance criteria 1. **met — Find all post types with existing defaults.** `runPostsFindCommand` registers the new command; its request defaults to `status=published` and omits `type`. API filtering retains the established publication predicate. CLI/API fixtures cover articles, links, and notes; live default draft-content search returns zero and explicit draft search finds the note. 2. **met — Full-field literal/Unicode matching and absent fields.** CLI and API compare title, slug, content, excerpt, and URL with JavaScript `toLowerCase().includes()`, matching existing sources/account/contact conventions. Queries are trimmed before searching, optional/null fields are safely skipped, and omitted summary content triggers detail reads when needed. Tests and live evidence cover mixed-case École, content-only matches, excerpts, URLs with query characters, and literal `%_ [` rather than SQL wildcards or regular expressions. 3. **met — Combined filters preserve semantics.** Existing parameterized type/status/tag predicates remain intact and combine with text matching. CLI reuses post-flag/query helpers, including first repeated tag handling; API retains tag normalization. Automated cases exercise draft/all/default status and type/tag intersections, including empty intersections; live logs show effective combined filters. 4. **met — Optional API search before pagination without regressions.** `listPosts` filters eligible complete rows before `slice(offset, offset + limit)` for nonblank queries. The absent/blank branch keeps SQL `LIMIT/OFFSET`; both branches retain timestamp/ID ordering and hydrate only the returned page into existing content-free summaries. Route response metadata and bearer authentication remain unchanged. Tests compare blank-query responses to baseline, check later matches beyond 105 filler rows, paging, validation, authentication, and unchanged records. No migrations or endpoint/package relocation are introduced. 5. **met — Complete updated/older-site traversal and failure propagation.** `findPosts` sends encoded `q`, requests up to 100 records, advances by actual returned length, and terminates only on an empty page, not a short page. Local verification handles older servers ignoring `q`; unresolved summaries fetch encoded-slug details. Repeated records and missing required detail content raise errors; request failures propagate before any results are printed. Tests cover both API versions, shortened pages, a content-only match after 100 records, repeated pages, failed details, and a later-page network failure. Live evidence confirms 106 aggregate matches and actual offset/count pairs `0/100`, `100/6`, `106/0`. 6. **met — Useful output, aggregate JSON, no-match success, read-only behavior.** Human output includes ID, slug, type, title or `(untitled)`, and publication status for follow-up `post show`. JSON returns original matching summaries in `{ "data": [...] }` without misleading single-page metadata. Empty results produce `No posts found.` or an empty data array with success. Requests are GET-only, detail content is not added to output summaries, and automated/live before-and-after evidence shows unchanged posts. 7. **met — Usage validation and distinct operational failures.** Query and flag parsing precede site/client creation and requests; blank/missing queries, extra arguments, invalid type/status, unsupported flags, and missing values are tested as usage errors with zero requests. Standard configured-site resolution and the existing HTTP client's authentication/API/network error classification are reused. Tests explicitly cover detail 401/403/404/422/500 and later-page network failure rather than treating them as no matches. 8. **met — Regression coverage.** The diff adds 40 CLI and 23 API focused test cases, plus OpenAPI assertions. Coverage includes types/fields, nulls, Unicode/literals, defaults and combined filters, multiple/empty results, older/updated APIs, short/multiple pages and later content-only matches, human/JSON output, invalid input, read-only behavior, authentication and request failures. Existing command paths are unchanged except find registration/help. Supplied full-suite evidence records 314 CLI and 432 site tests passing, alongside the package suites. 9. **met — Safe successful-request logs and live verification.** The posts route explicitly allowlists trimmed query, effective publication status, supplied type/normalized tag, pagination, result count, method/path and HTTP status. It does not copy arbitrary query parameters, credentials, or post bodies. Tests assert the exact context, absence of credential/unrelated-parameter/content values, zero-result logging, and no success logs for auth/validation failures. Sanitized live API logs contain actual combined-filter pagination/count records and the content-only draft query with limit 1 returning one result then zero; that match was absent from the first 50 unfiltered records. 10. **met — Documentation and customization-safe optional upgrade.** CLI help and `cli/README.md`, post command/API specifications, OpenAPI parameters, and `docs/package-updates.md` cover fields, literal semantics, defaults, combined filters, examples, full pagination/detail reads, aggregate JSON/no-match behavior, compatibility, and errors. Upgrade guidance explicitly requires reviewed per-file changes preserving customizations, adapted tests, build/test checks, redeployment, and live verification; package-only updates and advisory `slug upgrade` do not rewrite copied endpoints. ### Quality and security assessment - Scope is limited to post search, its tests, logging, and documentation; existing commands and auth enforcement are preserved. The small CLI search module keeps traversal/matching separate from command/output handling and reuses established client/parser conventions. - Query text is not interpolated into SQL or used as a regex; existing SQL filters remain parameterized. Detail slugs and query parameters are encoded. Search does not perform mutations or publish/federation operations, and logs avoid response content and authorization material. - Documented tradeoffs were assessed, not treated as defects: nonblank API search scans eligible rows in memory on each request for consistent Unicode/literal behavior; older sites can require many sequential detail requests; offset pagination is not a snapshot during concurrent edits. The implementation is correct for a stable collection, detects repeated records, and the documentation explicitly advises against concurrent collection changes. No unrequested indexed-search, snapshot, or endpoint-migration requirement is imposed. - Blocking issues: **none**. - Warnings requiring correction or waiver: **none**. ### Verification and CI state - `/tmp/slug-post-find-pre-pr.log`: full pre-PR checks passed, including 314 CLI and 432 site tests. - `/tmp/slug-post-find-build.log`: all-workspace build passed. - `/tmp/slug-post-find-live.log` and `/tmp/slug-post-find-live-api.log`: reviewed sanitized live searches, actual pagination/count logs, and reported unchanged before/after records. - CI: **success** — supplied verified Forgejo `build-lint-test` run **#477** at reviewed head `1b8783e1c991b78e956c4811d89ca0a7b217b625`. This worker's `fj pr status 285` could not deserialize Forgejo's relative job URL; CI state relies on the explicitly supplied verified run/head evidence, not a successful status refresh. No CI rerun was requested or performed. ### Final verdict **approved** — criteria 10/10 met, 0 missing, 0 blockers, 0 warnings. Review approval is not merge authorization. **Next:** Wait for explicit human merge approval. Do not merge or close the task; CLI publication remains separate.
erik merged commit 4799fd8d2e into main 2026-09-24 21:08:54 -05:00
erik deleted branch feat/task-ba500871-post-find 2026-09-24 21:08:54 -05:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
erik/slugkit!285
No description provided.