···2525 }
26262727 // Fall back to checking raw record for legacy base_path
2828- if (isLeafletPublication(pub.record) && pub.record.base_path && isProductionDomain()) {
2828+ if (
2929+ isLeafletPublication(pub.record) &&
3030+ pub.record.base_path &&
3131+ isProductionDomain()
3232+ ) {
2933 return `https://${pub.record.base_path}`;
3034 }
3135···3640 const normalized = normalizePublicationRecord(pub.record);
3741 const aturi = new AtUri(pub.uri);
38423939- // Use normalized name if available, fall back to rkey
4040- const name = normalized?.name || aturi.rkey;
4343+ //use rkey, fallback to name
4444+ const name = aturi.rkey || normalized?.name;
4145 return `/lish/${aturi.host}/${encodeURIComponent(name || "")}`;
4246}
···11+-- Add sort_date computed column to documents table
22+-- This column stores the older of publishedAt (from JSON data) or indexed_at
33+-- Used for sorting feeds chronologically by when content was actually published
44+55+-- Create an immutable function to parse ISO 8601 timestamps from text
66+-- This is needed because direct ::timestamp cast is not immutable (accepts 'now', 'today', etc.)
77+-- The regex validates the format before casting to ensure immutability
88+CREATE OR REPLACE FUNCTION parse_iso_timestamp(text) RETURNS timestamptz
99+LANGUAGE sql IMMUTABLE STRICT AS $$
1010+ SELECT CASE
1111+ -- Match ISO 8601 format: YYYY-MM-DDTHH:MM:SS with optional fractional seconds and Z/timezone
1212+ WHEN $1 ~ '^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$' THEN
1313+ $1::timestamptz
1414+ ELSE
1515+ NULL
1616+ END
1717+$$;
1818+1919+ALTER TABLE documents
2020+ADD COLUMN sort_date timestamptz GENERATED ALWAYS AS (
2121+ LEAST(
2222+ COALESCE(parse_iso_timestamp(data->>'publishedAt'), indexed_at),
2323+ indexed_at
2424+ )
2525+) STORED;
2626+2727+-- Create index on sort_date for efficient ordering
2828+CREATE INDEX documents_sort_date_idx ON documents (sort_date DESC, uri DESC);