feat(visibility): pull augur onto the embed + privacy-overview rails

Augur landed (faa16fa89) with the visibility Picker + setVisibility
already in place — but no embed-resolver and no entry in the
/settings privacy registry. So flipping an omen to 'public' did
nothing visible, and the kill-switch couldn't see augur records
either. Closes both gaps.

- New EmbedSource `augur.entries` + resolveAugurEntries. Whitelist:
  claim + "{kind} · {vibe} · {outcome}" line. Personal fields
  (feltMeaning, expectedOutcome, source name, outcomeNote, related
  dream/decision links, livingOracleSnapshot) all stay private.
  Optional `status` filter maps to AugurOutcome so the user can
  build "predictions I got right" widgets.
- Sort: resolved-first, then encounteredAt desc — fulfilled
  predictions outrank still-open ones (more interesting public
  signal).
- Inspector dropdown gains "Augur (Omen / Wahrsagungen)".
- exposed-records.ts gains the augur entry — augur records now
  show up in /settings → Privatsphäre and the kill-switch.

Note: augur's `unlistedToken` field (set by its store on
'unlisted' flips) is currently dead code — the mana-api unlisted
backend doesn't know about `augurEntries` and there's no shared
view component. Half-state predates this commit; full unlisted-
share wiring is a separate, larger task that would touch the
backend's ALLOWED_COLLECTIONS, the resolvers blob, and a new
SharedAugurEntryView. Leaving as-is until there's clear demand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-25 15:18:14 +02:00
parent 21c64e2616
commit 1cb137c4ff
4 changed files with 87 additions and 0 deletions

View file

@ -235,6 +235,18 @@ const TABLES: TableConfig[] = [
return decksStore.setVisibility(id, next);
},
},
{
module: 'augur',
collection: 'augurEntries',
moduleLabel: 'Augur (Omen / Wahrsagungen)',
encrypted: true,
title: (r) => asString(r.claim),
href: (id) => `/augur/entry/${id}`,
setVisibility: async (id, next) => {
const { augurStore } = await import('$lib/modules/augur/stores/entries.svelte');
return augurStore.setVisibility(id, next);
},
},
];
/**

View file

@ -37,6 +37,7 @@ import type { LocalSocialEvent } from '$lib/modules/events/types';
import type { LocalMemo } from '$lib/modules/memoro/types';
import type { LocalDeck as LocalCardDeck } from '$lib/modules/cards/types';
import type { LocalDeck as LocalPresiDeck } from '$lib/modules/presi/types';
import type { LocalAugurEntry } from '$lib/modules/augur/types';
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
export interface ResolvedEmbed {
@ -96,6 +97,9 @@ export async function resolveEmbed(props: ModuleEmbedProps): Promise<ResolvedEmb
case 'presi.decks':
items = await resolvePresiDecks(props);
break;
case 'augur.entries':
items = await resolveAugurEntries(props);
break;
default:
return {
items: [],
@ -912,3 +916,72 @@ async function resolvePresiDecks(_props: ModuleEmbedProps): Promise<EmbedItem[]>
};
});
}
/**
* Augur (omens / fortunes / hunches): public-divination teaser.
* Returns entries flipped to 'public' with the kind + vibe + outcome
* status as subtitle.
*
* Whitelist: claim + "{kind} · {vibe} · {outcome}". The personal
* fields feltMeaning, expectedOutcome, expectedBy, source name,
* outcomeNote, related dream/decision links, livingOracleSnapshot
* all stay private. Augur captures are by default private (the
* store stamps `'private'` rather than space-default), so a flip
* to 'public' is an explicit "I want to share this prediction".
*
* Filters: optional `status` maps to AugurOutcome ('open' /
* 'fulfilled' / 'partly' / 'not-fulfilled') so the user can build
* "predictions I got right" or "still open" widgets.
*/
async function resolveAugurEntries(props: ModuleEmbedProps): Promise<EmbedItem[]> {
const KIND_LABEL: Record<string, string> = {
omen: 'Omen',
fortune: 'Wahrsagung',
hunch: 'Bauchgefühl',
};
const VIBE_LABEL: Record<string, string> = {
good: 'Gutes Zeichen',
bad: 'Warnung',
mysterious: 'Rätselhaft',
};
const OUTCOME_LABEL: Record<string, string> = {
open: 'Offen',
fulfilled: 'Eingetreten',
partly: 'Teilweise',
'not-fulfilled': 'Nicht eingetreten',
};
let entries = await db.table<LocalAugurEntry>('augurEntries').toArray();
entries = entries.filter(
(e) => !e.deletedAt && !e.isArchived && canEmbedOnWebsite(e.visibility ?? 'private')
);
if (props.filter?.status) {
entries = entries.filter((e) => e.outcome === props.filter?.status);
}
if (entries.length === 0) return [];
const decrypted = (await decryptRecords('augurEntries', entries)) as LocalAugurEntry[];
// Resolved-first then by encounteredAt desc — fulfilled predictions
// rank above open ones, which is the more interesting public signal.
decrypted.sort((a, b) => {
const aOpen = a.outcome === 'open' ? 1 : 0;
const bOpen = b.outcome === 'open' ? 1 : 0;
if (aOpen !== bOpen) return aOpen - bOpen;
return (b.encounteredAt ?? '').localeCompare(a.encounteredAt ?? '');
});
return decrypted.map((e) => {
const parts = [
KIND_LABEL[e.kind] ?? e.kind,
VIBE_LABEL[e.vibe] ?? e.vibe,
OUTCOME_LABEL[e.outcome] ?? e.outcome,
];
return {
title: e.claim,
subtitle: parts.join(' · '),
};
});
}

View file

@ -31,6 +31,7 @@
<option value="memoro.memos">Memoro</option>
<option value="cards.decks">Karten (Decks)</option>
<option value="presi.decks">Präsentationen</option>
<option value="augur.entries">Augur (Omen / Wahrsagungen)</option>
</select>
</label>

View file

@ -42,6 +42,7 @@ export const EmbedSourceSchema = z.enum([
'memoro.memos',
'cards.decks',
'presi.decks',
'augur.entries',
]);
export type EmbedSource = z.infer<typeof EmbedSourceSchema>;