···126126- GCN `data` payloads can be tens of KB, so list/search responses strip them
127127 server-side (`slim()` in the Lua scripts) and keep a human `summary`
128128 extracted from the event JSON.
129129+- **`.env` is a deploy-time artifact.** `podium deploy` writes it from
130130+ `.env.prod` or `.env.local`, and restores the local flavor after a prod
131131+ deploy (the prod copy lives on in the server's service dir). If local
132132+ `docker compose` ever fails on a `/srv/...` mount, `.env` has prod values —
133133+ `cp .env.local .env`.
134134+- **A dashboard login poisons same-origin XRPC.** HappyView allows anonymous
135135+ XRPC but hard-rejects *authenticated* calls without a client key — so once
136136+ you've logged into the dashboard, your session cookie would break the
137137+ frontend's fetches ("Missing client identification"). The frontend fetches
138138+ with `credentials: 'omit'` to stay anonymous; keep that if you touch
139139+ `api.ts`.
129140130141## Adding a data source
131142
+17-3
backend/scripts/search.lua
···4242 if not q or #q == 0 then error("missing required parameter: q") end
4343 local limit = math.min(tonumber(params.limit) or 25, 100)
44444545- local out, seen = {}, {}
4545+ -- One bucket per source, then a round-robin merge so a huge collection
4646+ -- can't crowd the others out of the results.
4747+ local buckets = {}
4648 for _, src in ipairs(SOURCES) do
4749 if not params.collection or params.collection == src.collection then
5050+ local bucket, seen = {}, {}
4851 for _, field in ipairs(src.fields) do
4952 local ok, res = pcall(db.search, {
5053 collection = src.collection,
···5356 limit = limit,
5457 })
5558 if ok then
5656- collect(out, seen, src.collection, res)
5959+ collect(bucket, seen, src.collection, res)
5760 else
5861 log("search failed on " .. src.collection .. "." .. field .. ": " .. tostring(res))
5962 end
6063 end
6464+ if #bucket > 0 then buckets[#buckets + 1] = bucket end
6165 end
6266 end
63676464- while #out > limit do table.remove(out) end
6868+ local out, rank, added = {}, 1, true
6969+ while #out < limit and added do
7070+ added = false
7171+ for _, bucket in ipairs(buckets) do
7272+ if bucket[rank] and #out < limit then
7373+ out[#out + 1] = bucket[rank]
7474+ added = true
7575+ end
7676+ end
7777+ rank = rank + 1
7878+ end
6579 return { q = q, results = toarray(out) }
6680end
+11
bin/podium.js
···216216 if (res.error?.code === 'ENOENT') fail('sm not found on PATH (npm link it from caddy-front)');
217217 if (res.status !== 0) fail(`sm deploy exited with ${res.status}`);
218218219219+ // The prod .env has been rsynced to the server; restore the local flavor
220220+ // so docker compose keeps working on this machine.
221221+ if (env === 'prod') {
222222+ try {
223223+ await copyFile(resolve(root, '.env.local'), resolve(root, '.env'));
224224+ log('.env ← .env.local (restored for local compose)');
225225+ } catch {
226226+ // no .env.local — leave the prod copy in place
227227+ }
228228+ }
229229+219230 if (flags.has('--no-push')) return;
220231 if (!envFile.vars.PODIUM_HV_KEY) {
221232 log('\nNo PODIUM_HV_KEY yet, skipping backend push. First-boot bootstrap:');