A per-user jz_bm_material_pins table and PUT/DELETE /materials/{id}/pin
on the student API and its token twins: 204 for a material the caller
may see, idempotent, 404 otherwise. List rows, course rows and the
detail carry pinned. The plain list lists the caller's pins first
(newest pin first); with q the relevance order stays. A pin writes only
the pins table and never changes the material's revision.
- jz_bm_material_file_texts caches the pdftotext output per file
- the River job jz.bm.extract_material_text extracts and resyncs
- the management multipart attach queues it inside its transaction
- file_texts in the document and a file snippet with its name
- GET /api/v1/bm/materials takes q: candidates over every material, in
rank order, with the snippet of the student list; 503 when the engine
cannot answer
- bm_materials and bm_manage_list_materials take q; under MCP the snippet
is plain text with ellipses where text was cut
- MCPMaterialPrivacy no longer claims no file text is ever returned
- README documents q, the snippet, the 503, the Search section and
bm:materials-reindex
- Material is a beachcomber Searchable (index jz_bm_materials) in the
hand-written models/material_search.go; jz.bm requires summercms v0.6.4
- classes.MaterialIndexer asks Typesense for up to 250 candidates with
body highlights; the app-wide Gate is on only for jz.bm-marked contexts
- GET /_bm/api/v1/materials takes q: candidates are re-gated, counted and
paged in SQL in rank order, each row with at most one plain snippet
- search failure answers 503 Search is unavailable.; without q the list
is unchanged