···4545- **Appview** (`appview/`) consumes the firehose to index published content
4646- **Feeds** (`feeds/`) provides subscription feeds for publications
47474848+### Lexicon Workflow
4949+5050+The source of truth for lexicon schemas is the TypeScript files in `lexicons/src/` (e.g. `facet.ts`, `publication.ts`, `blocks.ts`). The JSON files in `lexicons/pub/` are **generated output** — do not edit them directly, as `npm run lexgen` will overwrite them.
5151+5252+To add or modify a lexicon:
5353+1. Edit the relevant source file in `lexicons/src/`
5454+2. Run `npm run lexgen` to regenerate both the JSON schemas and the TypeScript API types in `lexicons/api/`
5555+4856### Key Directories
49575058- `app/` - Next.js App Router pages and API routes
···6270- **Replicache mutations**: Named handlers in `src/replicache/mutations.ts`, keep server mutations idempotent
6371- **React contexts**: `DocumentProvider`, `LeafletContentProvider` for page-level data
6472- **Inngest functions**: Async jobs in `app/api/inngest/functions/`
7373+- **Icons**: Icon components live in `components/Icons/`. Each icon is a named export in its own file (e.g. `RefreshSmall.tsx`), imports `Props` from `./Props`, spreads `{...props}` on the `<svg>` element, and uses `fill="currentColor"` instead of hardcoded colors like `fill="black"`.
···151151 <div className=" pt-0.5 flex flex-col text-sm text-tertiary ">
152152 <p className="font-bold italic">Show In Discover</p>
153153 <p className="text-sm text-tertiary font-normal">
154154- Your posts will appear on our{" "}
155155- <a href="/discover" target="_blank">
156156- Discover
157157- </a>{" "}
158158- page. You can change this at any time!
154154+ Your posts will appear in{" "}
155155+ <a href="/reader" target="_blank">
156156+ Leaflet Reader
157157+ </a>
158158+ . You can change this at any time!
159159 </p>
160160 </div>
161161 </Checkbox>
+6-11
app/lish/createPub/UpdatePubForm.tsx
···165165 onToggle={() => setShowInDiscover(!showInDiscover)}
166166 >
167167 <div className=" pt-0.5 flex flex-col text-sm text-tertiary ">
168168- <p className="font-bold">
169169- Show In{" "}
170170- <a href="/discover" target="_blank">
171171- Discover
168168+ <p className="font-bold italic">Show In Discover</p>
169169+ <p className="text-xs text-tertiary font-normal">
170170+ Your posts will appear in{" "}
171171+ <a href="/reader" target="_blank">
172172+ Leaflet Reader
172173 </a>
173173- </p>
174174- <p className="text-xs text-tertiary font-normal">
175175- Your posts will appear on our{" "}
176176- <a href="/discover" target="_blank">
177177- Discover
178178- </a>{" "}
179179- page. You can change this at any time!
174174+ . You can change this at any time!
180175 </p>
181176 </div>
182177 </Toggle>
···18181919 return (
2020 <div className="flex flex-col gap-1 max-w-full">
2121- <div>
2222- <h3 className="text-secondary">Add a New Domain</h3>
2323- <div className="text-xs italic text-secondary">
2424- Don't include the protocol or path, just the base domain name
2525- </div>
2121+ <h3>Add a Domain</h3>
2222+ <div className="text-sm text-secondary">
2323+ <div className="font-bold">Just include the base domain</div>
2424+ Don't include the protocol{" "}
2525+ <span className="text-tertiary">(like https://) </span>
2626+ or path <span className="text-tertiary">(you can add that later)</span>
2627 </div>
27282829 <Input
···9494 date_from: p.string().optional(),
9595 date_to: p.string().optional(),
9696 path: p.string().optional(),
9797+ referrer_host: p.string().optional(),
9798 },
9899 tokens: [PROD_READ_TOKEN],
99100 nodes: [
···116117 {% if defined(path) %}
117118 AND path = {{String(path)}}
118119 {% end %}
120120+ {% if defined(referrer_host) %}
121121+ AND domain(referrer) = {{String(referrer_host)}}
122122+ {% end %}
119123 GROUP BY day
120124 ORDER BY day ASC
121125 `,
···146150 date_from: p.string().optional(),
147151 date_to: p.string().optional(),
148152 path: p.string().optional(),
153153+ referrer_host: p.string().optional(),
149154 limit: p.int32().optional(10),
150155 },
151156 nodes: [
···169174 {% if defined(path) %}
170175 AND path = {{String(path)}}
171176 {% end %}
177177+ {% if defined(referrer_host) %}
178178+ AND domain(referrer) = {{String(referrer_host)}}
179179+ {% end %}
172180 GROUP BY referrer_host
173181 ORDER BY pageviews DESC
174182 LIMIT {{Int32(limit, 10)}}
···199207 domains: p.string(),
200208 date_from: p.string().optional(),
201209 date_to: p.string().optional(),
210210+ referrer_host: p.string().optional(),
202211 limit: p.int32().optional(10),
203212 },
204213 nodes: [
···216225 {% end %}
217226 {% if defined(date_to) %}
218227 AND fromUnixTimestamp64Milli(timestamp) <= parseDateTimeBestEffort({{String(date_to)}})
228228+ {% end %}
229229+ {% if defined(referrer_host) %}
230230+ AND domain(referrer) = {{String(referrer_host)}}
219231 {% end %}
220232 GROUP BY path
221233 ORDER BY pageviews DESC
+1-1
next-env.d.ts
···11/// <reference types="next" />
22/// <reference types="next/image-types/global" />
33-import "./.next/types/routes.d.ts";
33+import "./.next/dev/types/routes.d.ts";
4455// NOTE: This file should not be edited
66// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
···11+-- Fix search_tags function to handle documents where data->'tags' is not a JSON array
22+-- This prevents "cannot extract elements from an object" errors
33+CREATE OR REPLACE FUNCTION search_tags(search_query text)
44+RETURNS TABLE (name text, document_count bigint) AS $$
55+BEGIN
66+ RETURN QUERY
77+ SELECT
88+ LOWER(tag::text) as name,
99+ COUNT(DISTINCT d.uri) as document_count
1010+ FROM (
1111+ SELECT uri, data
1212+ FROM "public"."documents"
1313+ WHERE jsonb_typeof(data->'tags') = 'array'
1414+ ) d,
1515+ jsonb_array_elements_text(d.data->'tags') as tag
1616+ WHERE
1717+ CASE
1818+ WHEN search_query = '' THEN true
1919+ ELSE LOWER(tag::text) LIKE '%' || search_query || '%'
2020+ END
2121+ GROUP BY
2222+ LOWER(tag::text)
2323+ ORDER BY
2424+ COUNT(DISTINCT d.uri) DESC,
2525+ LOWER(tag::text) ASC
2626+ LIMIT 20;
2727+END;
2828+$$ LANGUAGE plpgsql STABLE;