From a2d9b64114983ebb23b7687901f2215daaccb873 Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 09:08:37 -0500
Subject: [PATCH 01/22] Add anchor comments to project Cursor instructions
---
.cursor/rules/alexandria.mdc | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/.cursor/rules/alexandria.mdc b/.cursor/rules/alexandria.mdc
index f45fffd..5f6e97e 100644
--- a/.cursor/rules/alexandria.mdc
+++ b/.cursor/rules/alexandria.mdc
@@ -36,14 +36,30 @@ When responding to prompts, adhere to the following rules:
- Avoid proposing code edits unless I specifically tell you to do so.
- When giving examples from my codebase, include the file name and line numbers so I can find the relevant code easily.
-## Code Style
+## AI Anchor Comments Format
-Observe the following style guidelines when writing code:
+- Use anchor comments prefixed with `AI-NOTE:`, `AI-TODO:`, or `AI-QUESTION:` to share context between AI agents and developers across time.
+ - Use all-caps prefixes.
+ - Also _read_ (but do not write) variants of this format that begin with `AI-:` where `` is some date in `MM/DD/YYYY` format. Anchor comments with this format are used by developers to record context.
+- **Important:** Before scanning files, ALWAYS search first for `AI-` anchor comments in relevant subdirectories.
+- ALWAYS update relevant anchor comments when modifying associated code.
+- NEVER remove `AI-` comments unless the developer explicitly instructs it.
+- Add new anchor comments as relevant when:
+ - Code is unusually complex.
+ - Code is critical to security, performance, or functionality.
+ - Code is confusing.
+ - Code could have a bug.
+
+## Coding Guidelines
+
+### Prime Directive
+
+NEVER assume developer intent. If you are unsure about something, ALWAYS stop and ask the developer for clarification before proceeding.
### General Guidance
- Use snake_case names for plain TypeScript files.
-- Use comments sparingly; code should be self-documenting.
+- Use comments sparingly; aim to make code readable and self-documenting.
### JavaScript/TypeScript
From 825dee1584521dd00b88349c099b618d62f307a6 Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 09:12:39 -0500
Subject: [PATCH 02/22] Specify Deno as the preferred runtime in Cursor rules
---
.cursor/rules/alexandria.mdc | 1 +
1 file changed, 1 insertion(+)
diff --git a/.cursor/rules/alexandria.mdc b/.cursor/rules/alexandria.mdc
index 5f6e97e..c9c9730 100644
--- a/.cursor/rules/alexandria.mdc
+++ b/.cursor/rules/alexandria.mdc
@@ -58,6 +58,7 @@ NEVER assume developer intent. If you are unsure about something, ALWAYS stop an
### General Guidance
+- Prefer to use Deno to manage dependencies, build the project, and run tests.
- Use snake_case names for plain TypeScript files.
- Use comments sparingly; aim to make code readable and self-documenting.
From 8a8bdd18acfe36747ad90a1bed2c62fe88d40bf1 Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 10:43:30 -0500
Subject: [PATCH 03/22] TEMP refactor plan for LLMs
---
.../components/publications/REFACTOR_PLAN.md | 35 +++++++++++
src/routes/publication/REFACTOR_PLAN.md | 60 +++++++++++++++++++
2 files changed, 95 insertions(+)
create mode 100644 src/lib/components/publications/REFACTOR_PLAN.md
create mode 100644 src/routes/publication/REFACTOR_PLAN.md
diff --git a/src/lib/components/publications/REFACTOR_PLAN.md b/src/lib/components/publications/REFACTOR_PLAN.md
new file mode 100644
index 0000000..f65f974
--- /dev/null
+++ b/src/lib/components/publications/REFACTOR_PLAN.md
@@ -0,0 +1,35 @@
+# Component Refactoring Plan for Path-Based Routing
+
+This document outlines the necessary changes to Svelte components to support the new path-based routing for publications.
+
+## 1. `PublicationHeader.svelte`
+
+This component generates links to publications and needs to be updated to the new URL format.
+
+### Actions:
+
+1. **Locate `href` derivation:** Find the `$derived.by` block that computes the `href` constant.
+2. **Update URL structure:** Modify the logic to generate URLs in the format `/publication/[type]/[identifier]`.
+ - If the event has a `d` tag and is a replaceable event (e.g., kind 30040), encode it as an `naddr` and use the URL `/publication/naddr/[naddr]`.
+ - If the event is not replaceable but has an ID (like a kind 30041), encode it as an `nevent` and use the URL `/publication/nevent/[nevent]`.
+ - Use the existing `naddrEncode` and `neventEncode` utilities from `src/lib/utils.ts` to encode identifiers.
+ - If needed, add new `naddrDecode` and `neventDecode` utilities to `src/lib/utils.ts`, leveraging functions from the `nip19` module in the `nostr-tools` package.
+
+## 2. `Publication.svelte`
+
+This component is responsible for rendering the publication content. The primary changes will be in how data is passed to it, rather than in its internal logic.
+
+### Actions:
+
+1. **Review props:** The component accepts `rootAddress`, `publicationType`, and `indexEvent`. This is good.
+2. **Update parent component:** The new `src/routes/publication/[type]/[identifier]/+page.svelte` will be responsible for providing these props from the data loaded on the server. No direct changes to `Publication.svelte` should be needed unless the data shape from the `load` function requires it. It is expected that the `load` function will provide the `indexEvent` directly.
+3. **Add identifierType prop:** If the rendering logic needs to know the original identifier type (e.g., `id`, `d`, `naddr`, `nevent`), introduce a new `identifierType` prop to `Publication.svelte`.
+
+## 3. General Codebase Audit
+
+Other parts of the application might contain hardcoded links to publications using the old query parameter format.
+
+### Actions:
+
+1. **Perform a codebase search:** Search for the strings `"publication?id="` and `"publication?d="` to identify any other places where links are constructed.
+2. **Update any found links:** Refactor any discovered instances to use the new `/publication/[type]/[identifier]` format.
\ No newline at end of file
diff --git a/src/routes/publication/REFACTOR_PLAN.md b/src/routes/publication/REFACTOR_PLAN.md
new file mode 100644
index 0000000..a7b9689
--- /dev/null
+++ b/src/routes/publication/REFACTOR_PLAN.md
@@ -0,0 +1,60 @@
+# Publication Route Refactoring Plan
+
+This document outlines the plan to refactor the publication routes to improve SSR, add server-side metadata, and switch to path-based routing.
+
+## 1. New Route Structure
+
+The current query-based routing (`/publication?id=...`) will be replaced with a path-based structure: `/publication/[type]/[identifier]`.
+
+### Supported Identifier Types:
+- `id`: A raw hex event ID.
+- `d`: A `d` tag identifier from a replaceable event.
+- `naddr`: A bech32-encoded `naddr` string for a replaceable event.
+- `nevent`: A bech32-encoded `nevent` string.
+
+### Actions:
+
+1. **Create new route directory:** `src/routes/publication/[type]/[identifier]`.
+2. **Move `+page.svelte`:** Relocate the content of the current `src/routes/publication/+page.svelte` to `src/routes/publication/[type]/[identifier]/+page.svelte`.
+3. **Preserve old query-based route:** Instead of deleting old files, create `src/routes/publication/+page.server.ts` at the root of `src/routes/publication` to parse `?id=` and `?d=` query parameters and delegate to the new path-based routes.
+4. **Review base route:** Ensure `/publication` either renders the main feed (via `PublicationFeed.svelte`) or redirects to `/start`; keep the existing `+page.svelte` in place for backward compatibility.
+
+## 2. Server-Side Rendering (SSR) and Data Loading
+
+We will use SvelteKit's `load` functions to fetch data on the server.
+
+### Actions:
+
+1. **Create `+page.server.ts`:** Inside `src/routes/publication/[type]/[identifier]/`, create a `+page.server.ts` file.
+2. **Implement `load` function:**
+ - The `load` function will receive `params` containing `type` and `identifier`.
+ - It will use these params to fetch the publication's root event. The logic will need to handle the different identifier types:
+ - If `type` is `id`, use the `identifier` as a hex event ID.
+ - If `type` is `d`, use the `identifier` to search for an event with a matching `d` tag; when multiple events share the same tag, select the event with the latest `created_at` timestamp. // AI-NOTE: choose latest for now; future logic may change.
+ - If `type` is `naddr` or `nevent`, decode the `identifier` using `nip19.decode()` (from `nostr-tools`) and construct the appropriate filter. Add corresponding `naddrDecode` and `neventDecode` functions to `src/lib/utils.ts` to centralize NIP-19 logic.
+ - The fetched event will be returned as `data` to the `+page.svelte` component.
+ - Handle cases where the event is not found by throwing a 404 error using `@sveltejs/kit/error`.
+
+## 3. Server-Side Metadata
+
+Publication-specific metadata will be rendered on the server for better link previews.
+
+### Actions:
+
+1. **Create `+layout.server.ts`:** Inside `src/routes/publication/[type]/[identifier]/`, create a `+layout.server.ts`. Its `load` function will be very similar to the one in `+page.server.ts`. It will fetch the root event and return the necessary data for metadata (title, summary, image URL).
+2. **Create `+layout.svelte`:** Inside `src/routes/publication/[type]/[identifier]/`, create a `+layout.svelte`.
+3. **Implement metadata:**
+ - The layout will receive `data` from its `load` function.
+ - It will contain a `` block.
+ - Inside ``, render `` and `` tags (OpenGraph, Twitter Cards) using properties from the loaded `data`.
+ - Use `{@render children()}` in `+layout.svelte` to display the page content.
+ - Refer to https://web.dev/learn/html/metadata/#officially_defined_meta_tags for a compilation of recommended meta tags.
+
+## 4. Handling Authentication
+
+For publications requiring authentication, we need to avoid full SSR of content while still providing a good user experience.
+
+### Actions:
+
+- Skip authentication/authorization handling in this refactor; it will be addressed separately.
+- If the `indexEvent` cannot be fetched, display a user-friendly error message in `+page.svelte` indicating the publication cannot be loaded.
\ No newline at end of file
From b665e1b019b50591e44e8ef7311d2a5ed8ab9f5d Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 10:54:37 -0500
Subject: [PATCH 04/22] AI - refactor for route params
Needs developer review
---
.../publications/PublicationHeader.svelte | 17 ++-
.../components/util/ContainingIndexes.svelte | 4 +-
.../util/ViewPublicationLink.svelte | 2 +-
.../navigator/EventNetwork/NodeTooltip.svelte | 2 +-
src/lib/utils.ts | 36 +++++
src/routes/about/+page.svelte | 4 +-
src/routes/publication/+page.server.ts | 25 ++++
.../[type]/[identifier]/+layout.server.ts | 132 ++++++++++++++++++
.../[type]/[identifier]/+layout.svelte | 28 ++++
.../[type]/[identifier]/+page.server.ts | 123 ++++++++++++++++
.../[type]/[identifier]/+page.svelte | 95 +++++++++++++
src/routes/start/+page.svelte | 10 +-
12 files changed, 463 insertions(+), 15 deletions(-)
create mode 100644 src/routes/publication/+page.server.ts
create mode 100644 src/routes/publication/[type]/[identifier]/+layout.server.ts
create mode 100644 src/routes/publication/[type]/[identifier]/+layout.svelte
create mode 100644 src/routes/publication/[type]/[identifier]/+page.server.ts
create mode 100644 src/routes/publication/[type]/[identifier]/+page.svelte
diff --git a/src/lib/components/publications/PublicationHeader.svelte b/src/lib/components/publications/PublicationHeader.svelte
index d0ed9b3..20a61a3 100644
--- a/src/lib/components/publications/PublicationHeader.svelte
+++ b/src/lib/components/publications/PublicationHeader.svelte
@@ -1,5 +1,5 @@
+
+
+
+ {metadata.title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{@render children()}
\ No newline at end of file
diff --git a/src/routes/publication/[type]/[identifier]/+page.server.ts b/src/routes/publication/[type]/[identifier]/+page.server.ts
new file mode 100644
index 0000000..f58dfe0
--- /dev/null
+++ b/src/routes/publication/[type]/[identifier]/+page.server.ts
@@ -0,0 +1,123 @@
+import { error } from "@sveltejs/kit";
+import type { PageServerLoad } from "./$types";
+import type { NDKEvent } from "@nostr-dev-kit/ndk";
+import { getActiveRelaySetAsNDKRelaySet } from "../../../../lib/ndk.ts";
+import { getMatchingTags } from "../../../../lib/utils/nostrUtils.ts";
+import { naddrDecode, neventDecode } from "../../../../lib/utils.ts";
+import type NDK from "@nostr-dev-kit/ndk";
+
+/**
+ * Fetches an event by hex ID
+ */
+async function fetchEventById(ndk: NDK, id: string): Promise {
+ try {
+ const event = await ndk.fetchEvent(id);
+ if (!event) {
+ throw new Error(`Event not found for ID: ${id}`);
+ }
+ return event;
+ } catch (err) {
+ throw error(404, `Failed to fetch publication root event.\n${err}`);
+ }
+}
+
+/**
+ * Fetches an event by d tag
+ */
+async function fetchEventByDTag(ndk: NDK, dTag: string): Promise {
+ try {
+ const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); // true for inbox relays
+ const events = await ndk.fetchEvents(
+ { "#d": [dTag] },
+ { closeOnEose: false },
+ relaySet,
+ );
+
+ if (!events || events.size === 0) {
+ throw new Error(`Event not found for d tag: ${dTag}`);
+ }
+
+ // AI-NOTE: Choose the event with the latest created_at timestamp when multiple events share the same d tag
+ const sortedEvents = Array.from(events).sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
+ return sortedEvents[0];
+ } catch (err) {
+ throw error(404, `Failed to fetch publication root event.\n${err}`);
+ }
+}
+
+/**
+ * Fetches an event by naddr identifier
+ */
+async function fetchEventByNaddr(ndk: NDK, naddr: string): Promise {
+ try {
+ const decoded = naddrDecode(naddr);
+ const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true);
+
+ const filter = {
+ kinds: [decoded.kind],
+ authors: [decoded.pubkey],
+ "#d": [decoded.identifier],
+ };
+
+ const event = await ndk.fetchEvent(filter, { closeOnEose: false }, relaySet);
+ if (!event) {
+ throw new Error(`Event not found for naddr: ${naddr}`);
+ }
+ return event;
+ } catch (err) {
+ throw error(404, `Failed to fetch publication root event.\n${err}`);
+ }
+}
+
+/**
+ * Fetches an event by nevent identifier
+ */
+async function fetchEventByNevent(ndk: NDK, nevent: string): Promise {
+ try {
+ const decoded = neventDecode(nevent);
+ const event = await ndk.fetchEvent(decoded.id);
+ if (!event) {
+ throw new Error(`Event not found for nevent: ${nevent}`);
+ }
+ return event;
+ } catch (err) {
+ throw error(404, `Failed to fetch publication root event.\n${err}`);
+ }
+}
+
+export const load: PageServerLoad = async ({ params, parent }) => {
+ const { type, identifier } = params;
+ const { ndk } = await parent();
+
+ if (!ndk) {
+ throw error(500, "NDK not available");
+ }
+
+ let indexEvent: NDKEvent;
+
+ // Handle different identifier types
+ switch (type) {
+ case 'id':
+ indexEvent = await fetchEventById(ndk, identifier);
+ break;
+ case 'd':
+ indexEvent = await fetchEventByDTag(ndk, identifier);
+ break;
+ case 'naddr':
+ indexEvent = await fetchEventByNaddr(ndk, identifier);
+ break;
+ case 'nevent':
+ indexEvent = await fetchEventByNevent(ndk, identifier);
+ break;
+ default:
+ throw error(400, `Unsupported identifier type: ${type}`);
+ }
+
+ const publicationType = getMatchingTags(indexEvent, "type")[0]?.[1];
+
+ return {
+ publicationType,
+ indexEvent,
+ ndk, // Pass ndk to the page for the publication tree
+ };
+};
\ No newline at end of file
diff --git a/src/routes/publication/[type]/[identifier]/+page.svelte b/src/routes/publication/[type]/[identifier]/+page.svelte
new file mode 100644
index 0000000..9f37442
--- /dev/null
+++ b/src/routes/publication/[type]/[identifier]/+page.svelte
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/routes/start/+page.svelte b/src/routes/start/+page.svelte
index ff617c6..6cb37a3 100644
--- a/src/routes/start/+page.svelte
+++ b/src/routes/start/+page.svelte
@@ -91,7 +91,7 @@
Our own team uses Alexandria to document the app, to display our blog entriesblog entries, as well as to store copies of our most interesting
- technical specifications.
@@ -168,7 +168,7 @@
collaborative knowledge bases and documentation. Wiki pages, such as this
one about the goto("/publication/d/sybil")}>Sybil utility use the same Asciidoc format as other publications but are specifically designed
for interconnected, evolving content.
From 2ce83b75cad1ac305bab32976b2a7ad274983c4b Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 14:09:06 -0500
Subject: [PATCH 05/22] Set return types for NIP-19 decoding utils
---
src/lib/utils.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 970bdba..bc2a2ab 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,6 +1,7 @@
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { nip19 } from "nostr-tools";
import { getMatchingTags } from "./utils/nostrUtils.ts";
+import { AddressPointer, EventPointer } from "nostr-tools/nip19";
export function neventEncode(event: NDKEvent, relays: string[]) {
return nip19.neventEncode({
@@ -32,7 +33,7 @@ export function nprofileEncode(pubkey: string, relays: string[]) {
/**
* Decodes an naddr identifier and returns the decoded data
*/
-export function naddrDecode(naddr: string) {
+export function naddrDecode(naddr: string): AddressPointer {
try {
if (!naddr.startsWith('naddr')) {
throw new Error('Invalid naddr format');
@@ -50,7 +51,7 @@ export function naddrDecode(naddr: string) {
/**
* Decodes an nevent identifier and returns the decoded data
*/
-export function neventDecode(nevent: string) {
+export function neventDecode(nevent: string): EventPointer {
try {
if (!nevent.startsWith('nevent')) {
throw new Error('Invalid nevent format');
From 114b3a035b1e8b001743a45e4b541990db68dfae Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 14:14:05 -0500
Subject: [PATCH 06/22] Simplify link generation in `PublicationHeader`
component
The component only needs to work with kind 30040 index events.
---
.../publications/PublicationHeader.svelte | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/src/lib/components/publications/PublicationHeader.svelte b/src/lib/components/publications/PublicationHeader.svelte
index 20a61a3..c293fc9 100644
--- a/src/lib/components/publications/PublicationHeader.svelte
+++ b/src/lib/components/publications/PublicationHeader.svelte
@@ -7,6 +7,7 @@
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import LazyImage from "$components/util/LazyImage.svelte";
import { generateDarkPastelColor } from "$lib/utils/image_utils";
+ import { indexKind } from "$lib/consts";
const { event } = $props<{ event: NDKEvent }>();
@@ -19,20 +20,16 @@
});
const href = $derived.by(() => {
- const d = event.getMatchingTags("d")[0]?.[1];
- const isReplaceableEvent = event.kind === 30040 || event.kind === 30041;
+ const dTag = event.getMatchingTags("d")[0]?.[1];
+ const isIndexEvent = event.kind === indexKind;
- if (d != null && isReplaceableEvent) {
- // For replaceable events with d tag, use naddr encoding
+ if (dTag != null && isIndexEvent) {
+ // For index events with d tag, use naddr encoding
const naddr = naddrEncode(event, relays);
return `publication/naddr/${naddr}`;
- } else if (event.id) {
- // For non-replaceable events or events without d tag, use nevent encoding
- const nevent = neventEncode(event, relays);
- return `publication/nevent/${nevent}`;
} else {
// Fallback to d tag if available
- return d ? `publication/d/${d}` : null;
+ return dTag ? `publication/d/${dTag}` : null;
}
});
From 2150a311ecfb5575ac96e2f79e1ac2fa94e9af8c Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 14:24:14 -0500
Subject: [PATCH 07/22] Revise general project instructions
---
.cursor/rules/alexandria.mdc | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/.cursor/rules/alexandria.mdc b/.cursor/rules/alexandria.mdc
index c9c9730..61e8b28 100644
--- a/.cursor/rules/alexandria.mdc
+++ b/.cursor/rules/alexandria.mdc
@@ -9,11 +9,7 @@ You are senior full-stack software engineer with 20 years of experience writing
## Project Overview
-Alexandria is a Nostr project written in Svelte 5 and SvelteKit 2. It is a web app for reading, commenting on, and publishing books, blogs, and other long-form content stored on Nostr relays. It revolves around breaking long AsciiDoc documents into Nostr events, with each event containing a paragraph or so of text from the document. These individual content events are organized by index events into publications. An index contains an ordered list of references to other index events or content events, forming a tree.
-
-### Reader Features
-
-In reader mode, Alexandria loads a document tree from a root publication index event. The AsciiDoc text content of the various content events, along with headers specified by tags in the index events, is composed and rendered as a single document from the user's point of view.
+Alexandria is a Nostr project written in Svelte 5 and SvelteKit 2. It is a web app for reading, commenting on, and publishing books, blogs, and other long-form content stored on Nostr relays.
### Tech Stack
@@ -36,7 +32,13 @@ When responding to prompts, adhere to the following rules:
- Avoid proposing code edits unless I specifically tell you to do so.
- When giving examples from my codebase, include the file name and line numbers so I can find the relevant code easily.
-## AI Anchor Comments Format
+## Coding Guidelines
+
+### Prime Directive
+
+NEVER assume developer intent. If you are unsure about something, ALWAYS stop and ask the developer for clarification before proceeding.
+
+### AI Anchor Comments
- Use anchor comments prefixed with `AI-NOTE:`, `AI-TODO:`, or `AI-QUESTION:` to share context between AI agents and developers across time.
- Use all-caps prefixes.
@@ -50,12 +52,6 @@ When responding to prompts, adhere to the following rules:
- Code is confusing.
- Code could have a bug.
-## Coding Guidelines
-
-### Prime Directive
-
-NEVER assume developer intent. If you are unsure about something, ALWAYS stop and ask the developer for clarification before proceeding.
-
### General Guidance
- Prefer to use Deno to manage dependencies, build the project, and run tests.
From 1bd29506798a9420123330c3a5242f47b00611ec Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 14:26:42 -0500
Subject: [PATCH 08/22] Clean up redirects on query param based routes
---
src/routes/publication/+page.server.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/routes/publication/+page.server.ts b/src/routes/publication/+page.server.ts
index c753d79..29fc5a6 100644
--- a/src/routes/publication/+page.server.ts
+++ b/src/routes/publication/+page.server.ts
@@ -1,7 +1,7 @@
import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
-export const load: PageServerLoad = async ({ url }) => {
+export const load: PageServerLoad = ({ url }) => {
const id = url.searchParams.get("id");
const dTag = url.searchParams.get("d");
@@ -20,6 +20,7 @@ export const load: PageServerLoad = async ({ url }) => {
throw redirect(301, `/publication/d/${dTag}`);
}
- // If no query parameters, redirect to the start page or show publication feed
+ // If no query parameters, redirect to the start page or show publication feed\
+ // AI-TODO: Redirect to a "not found" page.
throw redirect(301, "/start");
};
\ No newline at end of file
From e093ee599d20b5ca110d9d5f9bbb90fdc5a9d9f6 Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 14:28:44 -0500
Subject: [PATCH 09/22] Give explicit instructions on reading and updating
anchor comemnts
---
.cursor/rules/alexandria.mdc | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.cursor/rules/alexandria.mdc b/.cursor/rules/alexandria.mdc
index 61e8b28..15ee6d0 100644
--- a/.cursor/rules/alexandria.mdc
+++ b/.cursor/rules/alexandria.mdc
@@ -54,6 +54,8 @@ NEVER assume developer intent. If you are unsure about something, ALWAYS stop an
### General Guidance
+- Before writing any code, ALWAYS search the codebase for relevant anchor comments.
+- Whenever updating code, ALWAYS update relevant anchor comments.
- Prefer to use Deno to manage dependencies, build the project, and run tests.
- Use snake_case names for plain TypeScript files.
- Use comments sparingly; aim to make code readable and self-documenting.
From 4a4b29f46c4a0aacf1113db59d54eb7c9c1b839d Mon Sep 17 00:00:00 2001
From: buttercat1791
Date: Tue, 29 Jul 2025 15:16:11 -0500
Subject: [PATCH 10/22] Clean up old pages and add TODOs to new ones
---
.../components/publications/REFACTOR_PLAN.md | 35 -----
src/routes/+layout.ts | 2 -
src/routes/publication/+page.svelte | 134 ------------------
src/routes/publication/+page.ts | 115 ---------------
src/routes/publication/REFACTOR_PLAN.md | 60 --------
.../[type]/[identifier]/+layout.server.ts | 2 +
.../[type]/[identifier]/+page.server.ts | 2 +
.../[type]/[identifier]/+page.svelte | 10 +-
8 files changed, 9 insertions(+), 351 deletions(-)
delete mode 100644 src/lib/components/publications/REFACTOR_PLAN.md
delete mode 100644 src/routes/publication/+page.svelte
delete mode 100644 src/routes/publication/+page.ts
delete mode 100644 src/routes/publication/REFACTOR_PLAN.md
diff --git a/src/lib/components/publications/REFACTOR_PLAN.md b/src/lib/components/publications/REFACTOR_PLAN.md
deleted file mode 100644
index f65f974..0000000
--- a/src/lib/components/publications/REFACTOR_PLAN.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Component Refactoring Plan for Path-Based Routing
-
-This document outlines the necessary changes to Svelte components to support the new path-based routing for publications.
-
-## 1. `PublicationHeader.svelte`
-
-This component generates links to publications and needs to be updated to the new URL format.
-
-### Actions:
-
-1. **Locate `href` derivation:** Find the `$derived.by` block that computes the `href` constant.
-2. **Update URL structure:** Modify the logic to generate URLs in the format `/publication/[type]/[identifier]`.
- - If the event has a `d` tag and is a replaceable event (e.g., kind 30040), encode it as an `naddr` and use the URL `/publication/naddr/[naddr]`.
- - If the event is not replaceable but has an ID (like a kind 30041), encode it as an `nevent` and use the URL `/publication/nevent/[nevent]`.
- - Use the existing `naddrEncode` and `neventEncode` utilities from `src/lib/utils.ts` to encode identifiers.
- - If needed, add new `naddrDecode` and `neventDecode` utilities to `src/lib/utils.ts`, leveraging functions from the `nip19` module in the `nostr-tools` package.
-
-## 2. `Publication.svelte`
-
-This component is responsible for rendering the publication content. The primary changes will be in how data is passed to it, rather than in its internal logic.
-
-### Actions:
-
-1. **Review props:** The component accepts `rootAddress`, `publicationType`, and `indexEvent`. This is good.
-2. **Update parent component:** The new `src/routes/publication/[type]/[identifier]/+page.svelte` will be responsible for providing these props from the data loaded on the server. No direct changes to `Publication.svelte` should be needed unless the data shape from the `load` function requires it. It is expected that the `load` function will provide the `indexEvent` directly.
-3. **Add identifierType prop:** If the rendering logic needs to know the original identifier type (e.g., `id`, `d`, `naddr`, `nevent`), introduce a new `identifierType` prop to `Publication.svelte`.
-
-## 3. General Codebase Audit
-
-Other parts of the application might contain hardcoded links to publications using the old query parameter format.
-
-### Actions:
-
-1. **Perform a codebase search:** Search for the strings `"publication?id="` and `"publication?d="` to identify any other places where links are constructed.
-2. **Update any found links:** Refactor any discovered instances to use the new `/publication/[type]/[identifier]` format.
\ No newline at end of file
diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts
index 80555ad..7ac4f69 100644
--- a/src/routes/+layout.ts
+++ b/src/routes/+layout.ts
@@ -9,8 +9,6 @@ import Pharos, { pharosInstance } from "../lib/parser.ts";
import type { LayoutLoad } from "./$types";
import { get } from "svelte/store";
-export const ssr = false;
-
export const load: LayoutLoad = () => {
// Initialize NDK with new relay management system
const ndk = initNdk();
diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte
deleted file mode 100644
index eacc71b..0000000
--- a/src/routes/publication/+page.svelte
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/routes/publication/+page.ts b/src/routes/publication/+page.ts
deleted file mode 100644
index a79423f..0000000
--- a/src/routes/publication/+page.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { error } from "@sveltejs/kit";
-import type { Load } from "@sveltejs/kit";
-import type { NDKEvent } from "@nostr-dev-kit/ndk";
-import { nip19 } from "nostr-tools";
-import { getActiveRelaySetAsNDKRelaySet } from "../../lib/ndk.ts";
-import { getMatchingTags } from "../../lib/utils/nostrUtils.ts";
-import type NDK from "@nostr-dev-kit/ndk";
-
-/**
- * Decodes an naddr identifier and returns a filter object
- */
-function decodeNaddr(id: string) {
- try {
- if (!id.startsWith("naddr")) return {};
-
- const decoded = nip19.decode(id);
- if (decoded.type !== "naddr") return {};
-
- const data = decoded.data;
- return {
- kinds: [data.kind],
- authors: [data.pubkey],
- "#d": [data.identifier],
- };
- } catch (e) {
- console.error("Failed to decode naddr:", e);
- return null;
- }
-}
-
-/**
- * Fetches an event by ID or filter
- */
-async function fetchEventById(ndk: NDK, id: string): Promise {
- const filter = decodeNaddr(id);
-
- // Handle the case where filter is null (decoding error)
- if (filter === null) {
- // If we can't decode the naddr, try using the raw ID
- try {
- const event = await ndk.fetchEvent(id);
- if (!event) {
- throw new Error(`Event not found for ID: ${id}`);
- }
- return event;
- } catch (err) {
- throw error(404, `Failed to fetch publication root event.\n${err}`);
- }
- }
-
- const hasFilter = Object.keys(filter).length > 0;
-
- try {
- const event = await (hasFilter
- ? ndk.fetchEvent(filter)
- : ndk.fetchEvent(id));
-
- if (!event) {
- throw new Error(`Event not found for ID: ${id}`);
- }
- return event;
- } catch (err) {
- throw error(404, `Failed to fetch publication root event.\n${err}`);
- }
-}
-
-/**
- * Fetches an event by d tag
- */
-async function fetchEventByDTag(ndk: NDK, dTag: string): Promise {
- try {
- const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); // true for inbox relays
- const event = await ndk.fetchEvent(
- { "#d": [dTag] },
- { closeOnEose: false },
- relaySet,
- );
-
- if (!event) {
- throw new Error(`Event not found for d tag: ${dTag}`);
- }
- return event;
- } catch (err) {
- throw error(404, `Failed to fetch publication root event.\n${err}`);
- }
-}
-
-// TODO: Use path params instead of query params.
-export const load: Load = async ({
- url,
- parent,
-}: {
- url: URL;
- parent: () => Promise>>;
-}) => {
- const id = url.searchParams.get("id");
- const dTag = url.searchParams.get("d");
- const { ndk } = await parent();
-
- if (!id && !dTag) {
- throw error(400, "No publication root event ID or d tag provided.");
- }
-
- // Fetch the event based on available parameters
- const indexEvent = id
- ? await fetchEventById(ndk!, id)
- : await fetchEventByDTag(ndk!, dTag!);
-
- const publicationType = getMatchingTags(indexEvent, "type")[0]?.[1];
-
- return {
- publicationType,
- indexEvent,
- };
-};
diff --git a/src/routes/publication/REFACTOR_PLAN.md b/src/routes/publication/REFACTOR_PLAN.md
deleted file mode 100644
index a7b9689..0000000
--- a/src/routes/publication/REFACTOR_PLAN.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Publication Route Refactoring Plan
-
-This document outlines the plan to refactor the publication routes to improve SSR, add server-side metadata, and switch to path-based routing.
-
-## 1. New Route Structure
-
-The current query-based routing (`/publication?id=...`) will be replaced with a path-based structure: `/publication/[type]/[identifier]`.
-
-### Supported Identifier Types:
-- `id`: A raw hex event ID.
-- `d`: A `d` tag identifier from a replaceable event.
-- `naddr`: A bech32-encoded `naddr` string for a replaceable event.
-- `nevent`: A bech32-encoded `nevent` string.
-
-### Actions:
-
-1. **Create new route directory:** `src/routes/publication/[type]/[identifier]`.
-2. **Move `+page.svelte`:** Relocate the content of the current `src/routes/publication/+page.svelte` to `src/routes/publication/[type]/[identifier]/+page.svelte`.
-3. **Preserve old query-based route:** Instead of deleting old files, create `src/routes/publication/+page.server.ts` at the root of `src/routes/publication` to parse `?id=` and `?d=` query parameters and delegate to the new path-based routes.
-4. **Review base route:** Ensure `/publication` either renders the main feed (via `PublicationFeed.svelte`) or redirects to `/start`; keep the existing `+page.svelte` in place for backward compatibility.
-
-## 2. Server-Side Rendering (SSR) and Data Loading
-
-We will use SvelteKit's `load` functions to fetch data on the server.
-
-### Actions:
-
-1. **Create `+page.server.ts`:** Inside `src/routes/publication/[type]/[identifier]/`, create a `+page.server.ts` file.
-2. **Implement `load` function:**
- - The `load` function will receive `params` containing `type` and `identifier`.
- - It will use these params to fetch the publication's root event. The logic will need to handle the different identifier types:
- - If `type` is `id`, use the `identifier` as a hex event ID.
- - If `type` is `d`, use the `identifier` to search for an event with a matching `d` tag; when multiple events share the same tag, select the event with the latest `created_at` timestamp. // AI-NOTE: choose latest for now; future logic may change.
- - If `type` is `naddr` or `nevent`, decode the `identifier` using `nip19.decode()` (from `nostr-tools`) and construct the appropriate filter. Add corresponding `naddrDecode` and `neventDecode` functions to `src/lib/utils.ts` to centralize NIP-19 logic.
- - The fetched event will be returned as `data` to the `+page.svelte` component.
- - Handle cases where the event is not found by throwing a 404 error using `@sveltejs/kit/error`.
-
-## 3. Server-Side Metadata
-
-Publication-specific metadata will be rendered on the server for better link previews.
-
-### Actions:
-
-1. **Create `+layout.server.ts`:** Inside `src/routes/publication/[type]/[identifier]/`, create a `+layout.server.ts`. Its `load` function will be very similar to the one in `+page.server.ts`. It will fetch the root event and return the necessary data for metadata (title, summary, image URL).
-2. **Create `+layout.svelte`:** Inside `src/routes/publication/[type]/[identifier]/`, create a `+layout.svelte`.
-3. **Implement metadata:**
- - The layout will receive `data` from its `load` function.
- - It will contain a `` block.
- - Inside ``, render `` and `` tags (OpenGraph, Twitter Cards) using properties from the loaded `data`.
- - Use `{@render children()}` in `+layout.svelte` to display the page content.
- - Refer to https://web.dev/learn/html/metadata/#officially_defined_meta_tags for a compilation of recommended meta tags.
-
-## 4. Handling Authentication
-
-For publications requiring authentication, we need to avoid full SSR of content while still providing a good user experience.
-
-### Actions:
-
-- Skip authentication/authorization handling in this refactor; it will be addressed separately.
-- If the `indexEvent` cannot be fetched, display a user-friendly error message in `+page.svelte` indicating the publication cannot be loaded.
\ No newline at end of file
diff --git a/src/routes/publication/[type]/[identifier]/+layout.server.ts b/src/routes/publication/[type]/[identifier]/+layout.server.ts
index 176b812..a9ddd3c 100644
--- a/src/routes/publication/[type]/[identifier]/+layout.server.ts
+++ b/src/routes/publication/[type]/[identifier]/+layout.server.ts
@@ -6,6 +6,8 @@ import { getMatchingTags } from "../../../../lib/utils/nostrUtils.ts";
import { naddrDecode, neventDecode } from "../../../../lib/utils.ts";
import type NDK from "@nostr-dev-kit/ndk";
+// AI-TODO: Use `fetchEventWithFallback` from `nostrUtils.ts` to retrieve events in this file.
+
/**
* Fetches an event by hex ID
*/
diff --git a/src/routes/publication/[type]/[identifier]/+page.server.ts b/src/routes/publication/[type]/[identifier]/+page.server.ts
index f58dfe0..3c033c7 100644
--- a/src/routes/publication/[type]/[identifier]/+page.server.ts
+++ b/src/routes/publication/[type]/[identifier]/+page.server.ts
@@ -6,6 +6,8 @@ import { getMatchingTags } from "../../../../lib/utils/nostrUtils.ts";
import { naddrDecode, neventDecode } from "../../../../lib/utils.ts";
import type NDK from "@nostr-dev-kit/ndk";
+// AI-TODO: Use `fetchEventWithFallback` from `nostrUtils.ts` to retrieve events in this file.
+
/**
* Fetches an event by hex ID
*/
diff --git a/src/routes/publication/[type]/[identifier]/+page.svelte b/src/routes/publication/[type]/[identifier]/+page.svelte
index 9f37442..07cf547 100644
--- a/src/routes/publication/[type]/[identifier]/+page.svelte
+++ b/src/routes/publication/[type]/[identifier]/+page.svelte
@@ -80,11 +80,11 @@
});
-
+
Date: Tue, 29 Jul 2025 16:26:14 -0500
Subject: [PATCH 11/22] Clean up and refactor based on AI code review
---
.../publications/PublicationFeed.svelte | 8 +-
.../navigator/EventNetwork/NodeTooltip.svelte | 2 +-
src/lib/utils.ts | 63 ++++++++-----
src/lib/utils/event_search.ts | 3 +-
.../advancedAsciidoctorPostProcessor.ts | 6 +-
src/lib/utils/network_detection.ts | 5 +-
src/lib/utils/nostrUtils.ts | 83 +++++++++++++++++
src/lib/utils/search_types.ts | 2 +-
src/routes/+layout.ts | 23 +++--
src/routes/publication/+page.server.ts | 33 +++++--
.../[type]/[identifier]/+layout.server.ts | 89 +------------------
.../[type]/[identifier]/+page.server.ts | 89 +------------------
12 files changed, 183 insertions(+), 223 deletions(-)
diff --git a/src/lib/components/publications/PublicationFeed.svelte b/src/lib/components/publications/PublicationFeed.svelte
index 674eb5a..8156cfe 100644
--- a/src/lib/components/publications/PublicationFeed.svelte
+++ b/src/lib/components/publications/PublicationFeed.svelte
@@ -290,9 +290,9 @@
};
// Debounced search function
- const debouncedSearch = debounce(async (query: string) => {
+ const debouncedSearch = debounce((query: string | undefined) => {
console.debug("[PublicationFeed] Search query changed:", query);
- if (query.trim()) {
+ if (query && query.trim()) {
const filtered = filterEventsBySearch(allIndexEvents);
eventsInView = filtered.slice(0, 30);
endOfFeed = filtered.length <= 30;
@@ -303,10 +303,6 @@
}, 300);
$effect(() => {
- console.debug(
- "[PublicationFeed] Search query effect triggered:",
- props.searchQuery,
- );
debouncedSearch(props.searchQuery);
});
diff --git a/src/lib/navigator/EventNetwork/NodeTooltip.svelte b/src/lib/navigator/EventNetwork/NodeTooltip.svelte
index 8066d4c..8e95b6e 100644
--- a/src/lib/navigator/EventNetwork/NodeTooltip.svelte
+++ b/src/lib/navigator/EventNetwork/NodeTooltip.svelte
@@ -145,7 +145,7 @@