Loading publication...
diff --git a/src/routes/publication/[type]/[identifier]/+page.ts b/src/routes/publication/[type]/[identifier]/+page.ts
index 1c00099..8f3bbaf 100644
--- a/src/routes/publication/[type]/[identifier]/+page.ts
+++ b/src/routes/publication/[type]/[identifier]/+page.ts
@@ -3,37 +3,78 @@ import type { PageLoad } from "./$types";
import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/websocket_utils.ts";
import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts";
-export const load: PageLoad = async ({ params }: { params: { type: string; identifier: string } }) => {
+export const load: PageLoad = async ({ params, parent }: { params: { type: string; identifier: string }; parent: any }) => {
const { type, identifier } = params;
+
+ // Get layout data (no server-side data since SSR is disabled)
+ const layoutData = await parent();
- let indexEvent: NostrEvent | null;
-
- // Handle different identifier types
- switch (type) {
- case 'id':
- indexEvent = await fetchEventById(identifier);
- break;
- case 'd':
- indexEvent = await fetchEventByDTag(identifier);
- break;
- case 'naddr':
- indexEvent = await fetchEventByNaddr(identifier);
- break;
- case 'nevent':
- indexEvent = await fetchEventByNevent(identifier);
- break;
- default:
- error(400, `Unsupported identifier type: ${type}`);
+ // AI-NOTE: Always fetch client-side since server-side fetch returns null for now
+ let indexEvent: NostrEvent | null = null;
+
+ try {
+ // Handle different identifier types
+ switch (type) {
+ case 'id':
+ indexEvent = await fetchEventById(identifier);
+ break;
+ case 'd':
+ indexEvent = await fetchEventByDTag(identifier);
+ break;
+ case 'naddr':
+ indexEvent = await fetchEventByNaddr(identifier);
+ break;
+ case 'nevent':
+ indexEvent = await fetchEventByNevent(identifier);
+ break;
+ default:
+ error(400, `Unsupported identifier type: ${type}`);
+ }
+ } catch (err) {
+ throw err;
}
-
+
if (!indexEvent) {
- error(404, `Event not found for ${type}: ${identifier}`);
+ // AI-NOTE: Handle case where no relays are available during preloading
+ // This prevents 404 errors when relay stores haven't been populated yet
+
+ // Create appropriate search link based on type
+ let searchParam = '';
+ switch (type) {
+ case 'id':
+ searchParam = `id=${identifier}`;
+ break;
+ case 'd':
+ searchParam = `d=${identifier}`;
+ break;
+ case 'naddr':
+ case 'nevent':
+ searchParam = `id=${identifier}`;
+ break;
+ default:
+ searchParam = `q=${identifier}`;
+ }
+
+ error(404, `Event not found for ${type}: ${identifier}. href="/events?${searchParam}"`);
}
const publicationType = indexEvent.tags.find((tag) => tag[0] === "type")?.[1] ?? "";
- return {
+ // AI-NOTE: Use proper NDK instance from layout or create one with relays
+ let ndk = layoutData?.ndk;
+ if (!ndk) {
+ // Import NDK dynamically to avoid SSR issues
+ const NDK = (await import("@nostr-dev-kit/ndk")).default;
+ // Import initNdk to get properly configured NDK with relays
+ const { initNdk } = await import("$lib/ndk");
+ ndk = initNdk();
+ }
+
+ const result = {
publicationType,
indexEvent,
+ ndk, // Use minimal NDK instance
};
+
+ return result;
};
diff --git a/tests/e2e/my_notes_layout.pw.spec.ts b/tests/e2e/my_notes_layout.pw.spec.ts
new file mode 100644
index 0000000..23db168
--- /dev/null
+++ b/tests/e2e/my_notes_layout.pw.spec.ts
@@ -0,0 +1,103 @@
+import { test, expect, type Page } from '@playwright/test';
+
+// Utility to check for horizontal scroll bar
+async function hasHorizontalScroll(page: Page, selector: string) {
+ return await page.evaluate((sel: string) => {
+ const el = document.querySelector(sel);
+ if (!el) return false;
+ return el.scrollWidth > el.clientWidth;
+ }, selector);
+}
+
+test.describe('My Notes Layout', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/my-notes');
+ await page.waitForSelector('h1:text("My Notes")');
+ });
+
+ test('no horizontal scroll bar for all tag type and tag filter combinations', async ({ page }) => {
+ // Helper to check scroll for current state
+ async function assertNoScroll() {
+ const hasScroll = await hasHorizontalScroll(page, 'main, body, html');
+ expect(hasScroll).toBeFalsy();
+ }
+
+ // Check default (no tag type selected)
+ await assertNoScroll();
+
+ // Get all tag type buttons
+ const tagTypeButtons = await page.locator('aside button').all();
+ // Only consider tag type buttons (first N)
+ const tagTypeCount = await page.locator('aside > div.flex.flex-wrap.gap-2.mb-6 > button').count();
+ // For each single tag type
+ for (let i = 0; i < tagTypeCount; i++) {
+ // Click tag type button
+ await tagTypeButtons[i].click();
+ await page.waitForTimeout(100); // Wait for UI update
+ await assertNoScroll();
+ // Get tag filter buttons (after tag type buttons)
+ const tagFilterButtons = await page.locator('aside > div.flex.flex-wrap.gap-2.mb-4 > button').all();
+ // Try all single tag filter selections
+ for (let j = 0; j < tagFilterButtons.length; j++) {
+ await tagFilterButtons[j].click();
+ await page.waitForTimeout(100);
+ await assertNoScroll();
+ // Deselect
+ await tagFilterButtons[j].click();
+ await page.waitForTimeout(50);
+ }
+ // Try all pairs of tag filter selections
+ for (let j = 0; j < tagFilterButtons.length; j++) {
+ for (let k = j + 1; k < tagFilterButtons.length; k++) {
+ await tagFilterButtons[j].click();
+ await tagFilterButtons[k].click();
+ await page.waitForTimeout(100);
+ await assertNoScroll();
+ // Deselect
+ await tagFilterButtons[j].click();
+ await tagFilterButtons[k].click();
+ await page.waitForTimeout(50);
+ }
+ }
+ // Deselect tag type
+ await tagTypeButtons[i].click();
+ await page.waitForTimeout(100);
+ }
+
+ // Try all pairs of tag type selections (multi-select)
+ for (let i = 0; i < tagTypeCount; i++) {
+ for (let j = i + 1; j < tagTypeCount; j++) {
+ await tagTypeButtons[i].click();
+ await tagTypeButtons[j].click();
+ await page.waitForTimeout(100);
+ await assertNoScroll();
+ // Get tag filter buttons for this combination
+ const tagFilterButtons = await page.locator('aside > div.flex.flex-wrap.gap-2.mb-4 > button').all();
+ // Try all single tag filter selections
+ for (let k = 0; k < tagFilterButtons.length; k++) {
+ await tagFilterButtons[k].click();
+ await page.waitForTimeout(100);
+ await assertNoScroll();
+ await tagFilterButtons[k].click();
+ await page.waitForTimeout(50);
+ }
+ // Try all pairs of tag filter selections
+ for (let k = 0; k < tagFilterButtons.length; k++) {
+ for (let l = k + 1; l < tagFilterButtons.length; l++) {
+ await tagFilterButtons[k].click();
+ await tagFilterButtons[l].click();
+ await page.waitForTimeout(100);
+ await assertNoScroll();
+ await tagFilterButtons[k].click();
+ await tagFilterButtons[l].click();
+ await page.waitForTimeout(50);
+ }
+ }
+ // Deselect tag types
+ await tagTypeButtons[i].click();
+ await tagTypeButtons[j].click();
+ await page.waitForTimeout(100);
+ }
+ }
+ });
+});
\ No newline at end of file