diff --git a/.gitignore b/.gitignore
index 380cac5b..5d549670 100644
--- a/.gitignore
+++ b/.gitignore
@@ -253,3 +253,4 @@ $RECYCLE.BIN/
# End of https://www.toptal.com/developers/gitignore/api/osx,windows,linux,nextjs,react,node
cdk.out
+.claude/
diff --git a/package-lock.json b/package-lock.json
index 55f2438d..32cf6cea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -38,7 +38,7 @@
"lucide-react": "^0.483.0",
"md5": "^2.3.0",
"mystjs": "^0.0.15",
- "next": "15.5.7",
+ "next": "15.5.9",
"next-themes": "^0.4.4",
"nextjs-toploader": "^3.8.16",
"node-fetch": "^3.3.2",
@@ -7209,9 +7209,9 @@
}
},
"node_modules/@next/env": {
- "version": "15.5.7",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz",
- "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
+ "version": "15.5.9",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
+ "integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
@@ -20383,12 +20383,12 @@
"license": "MIT"
},
"node_modules/next": {
- "version": "15.5.7",
- "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
- "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
+ "version": "15.5.9",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz",
+ "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==",
"license": "MIT",
"dependencies": {
- "@next/env": "15.5.7",
+ "@next/env": "15.5.9",
"@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
diff --git a/package.json b/package.json
index cdf01eaf..0217882e 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"lucide-react": "^0.483.0",
"md5": "^2.3.0",
"mystjs": "^0.0.15",
- "next": "15.5.7",
+ "next": "15.5.9",
"next-themes": "^0.4.4",
"nextjs-toploader": "^3.8.16",
"node-fetch": "^3.3.2",
diff --git a/scripts/dev-clean-start.sh b/scripts/dev-clean-start.sh
new file mode 100755
index 00000000..8af7c75d
--- /dev/null
+++ b/scripts/dev-clean-start.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+# Colors for output
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+CHECK="✓"
+ARROW="→"
+
+echo "${YELLOW}=== Clean Start for Source.coop Development ===${NC}"
+
+# Step 1: Clean up
+echo -e "\n${YELLOW}${ARROW} Cleaning up...${NC}"
+pkill -f "next dev" > /dev/null 2>&1
+docker-compose down > /dev/null 2>&1
+rm -rf .next
+echo -e "${GREEN}${CHECK} Cleaned up old processes and build cache${NC}"
+
+# Step 2: Start Docker services
+echo -e "\n${YELLOW}${ARROW} Starting Docker services (DynamoDB on :8000)...${NC}"
+docker-compose up -d
+echo -e "${GREEN}${CHECK} Docker services started${NC}"
+
+# Step 3: Wait for services
+echo -e "\n${YELLOW}${ARROW} Waiting for services to initialize...${NC}"
+sleep 10
+echo -e "${GREEN}${CHECK} Services ready${NC}"
+
+# Step 4: Start dev server without AWS profile
+echo -e "\n${YELLOW}${ARROW} Starting development server...${NC}"
+echo -e "${GREEN}Application will be available at: http://localhost:3000${NC}"
+echo -e "${GREEN}DynamoDB Admin UI at: http://localhost:8001${NC}"
+echo -e "\n${YELLOW}Press Ctrl+C to stop the development server${NC}\n"
+
+# Unset AWS profile to use local DynamoDB
+unset AWS_PROFILE
+unset AWS_DEFAULT_PROFILE
+npm run dev
+
diff --git a/src/components/features/markdown/HeadingWithPermalink.tsx b/src/components/features/markdown/HeadingWithPermalink.tsx
new file mode 100644
index 00000000..7a46382f
--- /dev/null
+++ b/src/components/features/markdown/HeadingWithPermalink.tsx
@@ -0,0 +1,26 @@
+import { ReactNode } from "react";
+import { Heading } from "@radix-ui/themes";
+import { Link2Icon } from "@radix-ui/react-icons";
+
+interface HeadingWithPermalinkProps {
+ id: string;
+ level: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
+ children: ReactNode;
+ mb?: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
+}
+
+export function HeadingWithPermalink({
+ id,
+ level,
+ children,
+ mb,
+}: HeadingWithPermalinkProps) {
+ return (
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/src/components/features/markdown/MarkdownViewer.tsx b/src/components/features/markdown/MarkdownViewer.tsx
index 1a69b3eb..08a36e8d 100644
--- a/src/components/features/markdown/MarkdownViewer.tsx
+++ b/src/components/features/markdown/MarkdownViewer.tsx
@@ -4,6 +4,7 @@ import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import { Code } from "./codeConfig";
+import { HeadingWithPermalink } from "./HeadingWithPermalink";
import "@/styles/MarkdownViewer.css";
interface MarkdownViewerProps {
@@ -38,39 +39,64 @@ export function MarkdownViewer({ content }: MarkdownViewerProps) {
}
return (
-
+ (
-
+
{children}
-
+
),
h2: ({ children }) => (
-
+
{children}
-
+
),
h3: ({ children }) => (
-
+
{children}
-
+
),
h4: ({ children }) => (
-
+
{children}
-
+
),
h5: ({ children }) => (
-
+
{children}
-
+
),
h6: ({ children }) => (
-
+
{children}
-
+
),
p: ({ children }) => (
@@ -78,7 +104,9 @@ export function MarkdownViewer({ content }: MarkdownViewerProps) {
),
a: ({ href, children }) => (
- {children}
+
+ {children}
+
),
table: ({ children }) => (
@@ -106,8 +134,9 @@ export function MarkdownViewer({ content }: MarkdownViewerProps) {
return {codeContent};
},
}}
- >
- {content}
-
+ >
+ {content}
+
+
);
}
diff --git a/src/styles/MarkdownViewer.css b/src/styles/MarkdownViewer.css
index b02e1984..e2d5437f 100644
--- a/src/styles/MarkdownViewer.css
+++ b/src/styles/MarkdownViewer.css
@@ -74,15 +74,6 @@ code.inline-code {
margin: 1em 0;
}
-/* Links */
-.markdown-viewer a {
- color: var(--blue-9);
- text-decoration: none;
-}
-
-.markdown-viewer a:hover {
- text-decoration: underline;
-}
/* Styles for MyST admonitions */
.admonition {
@@ -135,3 +126,29 @@ code.inline-code {
:root[class~="dark"] .admonition.important {
background-color: var(--purple-12);
}
+
+/* Permalink styles */
+.heading-with-permalink {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.permalink-link {
+ display: none;
+ color: var(--gray-9);
+ text-decoration: none;
+}
+
+.heading-with-permalink:hover .permalink-link {
+ display: inline-flex;
+}
+
+.permalink-link:hover {
+ color: var(--blue-9);
+}
+
+.permalink-link svg {
+ width: 0.85em;
+ height: 0.85em;
+}