diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75f5cda97..a4bf6c0ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,11 @@ jobs: - name: Setup uses: ./tooling/github/setup + with: + # Vitest + better-sqlite3 can abort during worker teardown on Node 24. + # Node 22.21.1 passed the TRPC and workers suites in CI run #124. + # Remove this override once nodejs/node#65042 ships in Node 24. + node-version: "22.21.1" - name: Shared Package Tests working-directory: packages/shared diff --git a/.nvmrc b/.nvmrc index a45fd52cc..8dfc5cb1a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24 +24.18.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e3b2f650c..600ccbae9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,13 +29,16 @@ Start here: ## Local setup This fork uses: -- Node 24 (`.nvmrc`) +- Node 24.18.1 (`.nvmrc`; temporarily pinned to avoid the Node 24.19 native-addon cleanup regression) - `pnpm@11.2.1` via corepack - root `.env` symlinked into `apps/web`, `apps/workers`, and `packages/db` Quick start: ```bash +nvm install +nvm use +corepack enable pnpm install ln -sf ../../.env apps/web/.env @@ -69,6 +72,7 @@ pnpm doctor:staged ``` Notes: +- GitHub Actions temporarily runs the combined test job on Node 22.21.1 because Vitest plus `better-sqlite3` can abort during worker teardown on Node 24. Local development and production remain on Node 24.18.1. Remove the CI override once ships in Node 24. - `pnpm doctor` and `pnpm doctor:staged` are advisory local checks. CI requires a React Doctor score of at least 99 through `pnpm doctor:ci`; see [`docs/react-doctor.md`](docs/react-doctor.md) for the baseline and accepted tool limitations. - `knip` is useful for repository cleanup, but is non-blocking in CI. diff --git a/apps/web/app/public/layout.tsx b/apps/web/app/public/layout.tsx index b4628e11e..b2841b35c 100644 --- a/apps/web/app/public/layout.tsx +++ b/apps/web/app/public/layout.tsx @@ -4,8 +4,10 @@ export default function PublicLayout({ children: React.ReactNode; }) { return ( -
-
{children}
+
+
+ {children} +
); } diff --git a/apps/web/app/public/lists/[listId]/not-found.tsx b/apps/web/app/public/lists/[listId]/not-found.tsx index 8eaa12848..63e4b6220 100644 --- a/apps/web/app/public/lists/[listId]/not-found.tsx +++ b/apps/web/app/public/lists/[listId]/not-found.tsx @@ -1,15 +1,37 @@ -import { EmptyState } from "@/components/shared/EmptyState"; -import { SearchX } from "lucide-react"; +import Link from "next/link"; +import MarkaLogo from "@/components/MarkaLogo"; +import { Button } from "@/components/ui/button"; +import { MARKA } from "@/lib/brand"; +import { Home, SearchX } from "lucide-react"; export default function PublicListPageNotFound() { return ( -
- } - title="List not found" - titleAs="h1" - description="The list you’re looking for doesn’t exist, is no longer public, or may have been removed." - /> +
+
+ + + +
+ +
+

+ This list isn’t available +

+

+ This list may have been made private, deleted, or the link may be + incorrect. +

+ +
); } diff --git a/apps/web/app/public/lists/[listId]/page.tsx b/apps/web/app/public/lists/[listId]/page.tsx index 7267b85d8..ccee9762a 100644 --- a/apps/web/app/public/lists/[listId]/page.tsx +++ b/apps/web/app/public/lists/[listId]/page.tsx @@ -47,28 +47,22 @@ export default async function PublicListPage(props: { await api.publicBookmarks.getPublicBookmarksInList({ listId: params.listId, }); + const publicList = { + id: params.listId, + name: list.name, + description: list.description, + icon: list.icon, + numItems: list.numItems, + ownerName: list.ownerName, + ownerImage: list.ownerImage, + }; + return ( -
- +
+ {list.numItems > 0 ? ( @@ -77,7 +71,7 @@ export default async function PublicListPage(props: { icon={} title="This public list is empty" description="The list has been shared, but it does not have any bookmarks in it yet." - className="shadow-xs rounded-2xl border-border/70 bg-card/90" + className="rounded-2xl border-border/80 bg-card" /> )}
diff --git a/apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx b/apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx index e881cbfe0..bc9f24502 100644 --- a/apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx +++ b/apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx @@ -210,8 +210,8 @@ export function ManageCollaboratorsModal({ {!readOnly && (
-
-
+
+
- - +
+ + +

{t("lists.collaborators.viewer")}:{" "} diff --git a/apps/web/components/dashboard/lists/PublicListLink.tsx b/apps/web/components/dashboard/lists/PublicListLink.tsx index 9cd1f795c..ca768820a 100644 --- a/apps/web/components/dashboard/lists/PublicListLink.tsx +++ b/apps/web/components/dashboard/lists/PublicListLink.tsx @@ -1,7 +1,6 @@ "use client"; import { CopyBtnV2 } from "@/components/ui/copy-button"; -import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { useClientConfig } from "@/lib/clientConfig"; @@ -10,6 +9,8 @@ import { useTranslation } from "react-i18next"; import { useEditBookmarkList } from "@karakeep/shared-react/hooks/lists"; import { ZBookmarkList } from "@karakeep/shared/types/lists"; +import { UrlDisplay } from "./UrlDisplay"; + export default function PublicListLink({ list }: { list: ZBookmarkList }) { const { t } = useTranslation(); const clientConfig = useClientConfig(); @@ -46,21 +47,18 @@ export default function PublicListLink({ list }: { list: ZBookmarkList }) { {/* Share URL - only show when public */} {isPublic && ( - <> -

- -
- - publicListUrl} /> -
+
+ +
+ + publicListUrl} />
- +
)} ); diff --git a/apps/web/components/dashboard/lists/RssLink.tsx b/apps/web/components/dashboard/lists/RssLink.tsx index 2ac53c934..8bfa4de67 100644 --- a/apps/web/components/dashboard/lists/RssLink.tsx +++ b/apps/web/components/dashboard/lists/RssLink.tsx @@ -3,7 +3,6 @@ import { useMemo } from "react"; import { Button } from "@/components/ui/button"; import { CopyBtnV2 } from "@/components/ui/copy-button"; -import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { useClientConfig } from "@/lib/clientConfig"; @@ -13,6 +12,8 @@ import { useTranslation } from "react-i18next"; import { useTRPC } from "@karakeep/shared-react/trpc"; +import { UrlDisplay } from "./UrlDisplay"; + export default function RssLink({ listId }: { listId: string }) { const api = useTRPC(); const { t } = useTranslation(); @@ -46,7 +47,7 @@ export default function RssLink({ listId }: { listId: string }) { return null; } return `${clientConfig.publicApiUrl}/v1/rss/lists/${listId}?token=${rssToken.token}`; - }, [rssToken]); + }, [clientConfig.publicApiUrl, listId, rssToken]); const rssEnabled = rssUrl !== null; @@ -82,9 +83,9 @@ export default function RssLink({ listId }: { listId: string }) { -
- - rssUrl} /> +
+ + rssUrl} />