The search crate (Plan 6) provides index_object / remove_object / reindex_all, but on-write sync is not wired — the index is only rebuilt via reindex_all. Plan 7 added set_visibility and the public read API, which makes index freshness more visible (a record published/unpublished should appear/disappear from search).
Meilisearch is not transactional with Postgres, so this is best-effort eventual consistency; reindex_all remains the recovery path.
What to do
At the API/service layer, after a catalogue create/update/delete/set_object_fields/set_visibilitycommits:
call search::SearchClient::index_object (re-project the object) or remove_object as appropriate;
log failures rather than failing the request (the write already committed);
consider: a record leaving public may need to drop from a future public-search index (public search itself is post-MVP — search already stores visibility as filterable).
Plans: docs/plans/2026-06-02-search.md, docs/plans/2026-06-02-publishing-public-api.md (Notes for follow-on plans)
## Context
The `search` crate (Plan 6) provides `index_object` / `remove_object` / `reindex_all`, but **on-write sync is not wired** — the index is only rebuilt via `reindex_all`. Plan 7 added `set_visibility` and the public read API, which makes index freshness more visible (a record published/unpublished should appear/disappear from search).
Meilisearch is not transactional with Postgres, so this is best-effort eventual consistency; `reindex_all` remains the recovery path.
## What to do
At the API/service layer, after a catalogue create/update/delete/`set_object_fields`/`set_visibility` **commits**:
- call `search::SearchClient::index_object` (re-project the object) or `remove_object` as appropriate;
- log failures rather than failing the request (the write already committed);
- consider: a record leaving `public` may need to drop from a future public-search index (public search itself is post-MVP — `search` already stores `visibility` as filterable).
## References
- `crates/search/src/lib.rs` — `index_object` / `remove_object` / `reindex_all` / `build_document`
- `crates/db/src/catalog.rs` — the write paths
- Plans: `docs/plans/2026-06-02-search.md`, `docs/plans/2026-06-02-publishing-public-api.md` (Notes for follow-on plans)
Scope note from the admin-CRUD work (merged main @ c4e0c4c): several new catalogue write paths now exist that mutate object data but do not trigger a search re-index, and should be covered by this issue alongside the publish/visibility writes:
POST /api/admin/objects (create)
PUT /api/admin/objects/{id} (update)
DELETE /api/admin/objects/{id} (delete)
PUT /api/admin/objects/{id}/fields (flexible-field replace)
All live in crates/api/src/admin_objects.rs. On-write reindexing should hook these in addition to the existing set_visibility path.
Scope note from the admin-CRUD work (merged `main` @ `c4e0c4c`): several **new catalogue write paths** now exist that mutate object data but do not trigger a search re-index, and should be covered by this issue alongside the publish/visibility writes:
- `POST /api/admin/objects` (create)
- `PUT /api/admin/objects/{id}` (update)
- `DELETE /api/admin/objects/{id}` (delete)
- `PUT /api/admin/objects/{id}/fields` (flexible-field replace)
All live in `crates/api/src/admin_objects.rs`. On-write reindexing should hook these in addition to the existing `set_visibility` path.
Implemented and merged to main (d15afda, cosmetic follow-up 4921c73).
What landed:
search::SearchClient::sync_object(db, id) — one uniform path: re-project + index_object if the object exists, remove_object if it's gone. Covers create/update/delete/fields/visibility with a single existence check.
AppState.search: Option<SearchClient> + a best-effort reindex(state, id) helper that logs failures via tracing::error! and never fails the committed write (reindex_all stays the recovery path).
Reindex wired after commit on all five write paths: create_object, update_object (when existed), delete_object (when existed), set_fields, set_visibility — success branches only.
Server config: MEILI_URL / MEILI_MASTER_KEY / MEILI_INDEX (default objects); indexing enabled only when URL+key are set, else disabled with a warn (no-op).
Tests: search/tests/sync.rs (index-then-remove) and api/tests/reindex.rs (HTTP create → indexed, delete → removed). Full workspace green.
Follow-up filed for the inline-reindex latency (background task). Closing.
Implemented and merged to `main` (`d15afda`, cosmetic follow-up `4921c73`).
**What landed:**
- `search::SearchClient::sync_object(db, id)` — one uniform path: re-project + `index_object` if the object exists, `remove_object` if it's gone. Covers create/update/delete/fields/visibility with a single existence check.
- `AppState.search: Option<SearchClient>` + a best-effort `reindex(state, id)` helper that logs failures via `tracing::error!` and **never fails the committed write** (`reindex_all` stays the recovery path).
- Reindex wired after commit on all five write paths: `create_object`, `update_object` (when existed), `delete_object` (when existed), `set_fields`, `set_visibility` — success branches only.
- Server config: `MEILI_URL` / `MEILI_MASTER_KEY` / `MEILI_INDEX` (default `objects`); indexing enabled only when URL+key are set, else disabled with a warn (no-op).
- Tests: `search/tests/sync.rs` (index-then-remove) and `api/tests/reindex.rs` (HTTP create → indexed, delete → removed). Full workspace green.
Follow-up filed for the inline-reindex latency (background task). Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context
The
searchcrate (Plan 6) providesindex_object/remove_object/reindex_all, but on-write sync is not wired — the index is only rebuilt viareindex_all. Plan 7 addedset_visibilityand the public read API, which makes index freshness more visible (a record published/unpublished should appear/disappear from search).Meilisearch is not transactional with Postgres, so this is best-effort eventual consistency;
reindex_allremains the recovery path.What to do
At the API/service layer, after a catalogue create/update/delete/
set_object_fields/set_visibilitycommits:search::SearchClient::index_object(re-project the object) orremove_objectas appropriate;publicmay need to drop from a future public-search index (public search itself is post-MVP —searchalready storesvisibilityas filterable).References
crates/search/src/lib.rs—index_object/remove_object/reindex_all/build_documentcrates/db/src/catalog.rs— the write pathsdocs/plans/2026-06-02-search.md,docs/plans/2026-06-02-publishing-public-api.md(Notes for follow-on plans)Scope note from the admin-CRUD work (merged
main@c4e0c4c): several new catalogue write paths now exist that mutate object data but do not trigger a search re-index, and should be covered by this issue alongside the publish/visibility writes:POST /api/admin/objects(create)PUT /api/admin/objects/{id}(update)DELETE /api/admin/objects/{id}(delete)PUT /api/admin/objects/{id}/fields(flexible-field replace)All live in
crates/api/src/admin_objects.rs. On-write reindexing should hook these in addition to the existingset_visibilitypath.Implemented and merged to
main(d15afda, cosmetic follow-up4921c73).What landed:
search::SearchClient::sync_object(db, id)— one uniform path: re-project +index_objectif the object exists,remove_objectif it's gone. Covers create/update/delete/fields/visibility with a single existence check.AppState.search: Option<SearchClient>+ a best-effortreindex(state, id)helper that logs failures viatracing::error!and never fails the committed write (reindex_allstays the recovery path).create_object,update_object(when existed),delete_object(when existed),set_fields,set_visibility— success branches only.MEILI_URL/MEILI_MASTER_KEY/MEILI_INDEX(defaultobjects); indexing enabled only when URL+key are set, else disabled with a warn (no-op).search/tests/sync.rs(index-then-remove) andapi/tests/reindex.rs(HTTP create → indexed, delete → removed). Full workspace green.Follow-up filed for the inline-reindex latency (background task). Closing.