Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
c466da6
docs(fumadocs-theme): add externalization spec & plan
yamcodes Jan 23, 2026
a7e19ba
Merge branch 'main' into 774-add-arkenvfumadocs-ui-theme
yamcodes Jan 23, 2026
b413dc9
Delete plan.md
yamcodes Jan 23, 2026
68df403
docs(theme): document Fumadocs UI theme publication
yamcodes Jan 23, 2026
9b1f1b6
docs(fumadocs-ui-theme): Refine theme proposal and spec
yamcodes Jan 23, 2026
6ab6648
feat(docs): extract custom UI into fumadocs theme
yamcodes Jan 23, 2026
b1886eb
refactor(url): move Arkenv URL utilities to theme package
yamcodes Jan 23, 2026
b1f8ba9
refactor: Centralize URL utilities and update imports
yamcodes Jan 23, 2026
3b748fe
refactor(deps): remove .ts extensions from imports
yamcodes Jan 23, 2026
e1cfe6a
fix(theme): remove extra quote from url import
yamcodes Jan 23, 2026
65c9b95
refactor(www): update ExternalLink import
yamcodes Jan 23, 2026
13e92f6
build(fumadocs-ui-theme): add tsdown build config
yamcodes Jan 23, 2026
8fb3972
build(theme): Create server-safe utils bundle
yamcodes Jan 23, 2026
acb8eb1
refactor(www): update fumadocs-ui-theme imports
yamcodes Jan 23, 2026
1685cab
Merge branch 'main' into 774-add-arkenvfumadocs-ui-theme
yamcodes Jan 23, 2026
2ea9459
build(bundle): separate MDX components bundle
yamcodes Jan 23, 2026
33fa3b1
refactor(ui-theme): update build config and import paths
yamcodes Jan 23, 2026
c70eef3
chore(ui-theme): disable ts extension imports in build
yamcodes Jan 23, 2026
acac27b
feat: Add direct export for `./utils` and update build script to forc…
yamcodes Jan 23, 2026
ebffc31
Merge branch 'main' into 774-add-arkenvfumadocs-ui-theme
yamcodes Jan 23, 2026
5af0c7e
feat: Add new components for AI actions, code blocks, and external li…
yamcodes Jan 23, 2026
245209c
feat: Integrate @arkenv/fumadocs-ui-theme components and utilities ac…
yamcodes Jan 23, 2026
0a7bfb6
fix: Set noEmit to true in tsconfig.json to prevent output generation
yamcodes Jan 23, 2026
6fb23ef
feat: Update build process to use tsdown and add typecheck script in …
yamcodes Jan 23, 2026
c6d1f01
feat: restructure fumadocs-ui package with new components and styles
yamcodes Jan 23, 2026
0ccb09b
feat: update imports to use @arkenv/fumadocs-ui instead of @arkenv/fu…
yamcodes Jan 23, 2026
5986ff8
feat: rename package from @arkenv/fumadocs-ui-theme to @arkenv/fumado…
yamcodes Jan 23, 2026
5a354d7
fix: update package reference from @arkenv/fumadocs-ui-theme to @arke…
yamcodes Jan 23, 2026
dc21132
feat: add MDX components and update exports in fumadocs-ui package
yamcodes Jan 23, 2026
5010234
fix: update theme import path in globals.css and enhance exports in p…
yamcodes Jan 23, 2026
ed50b90
fix: update import paths for ExternalLink and AIActions to use compon…
yamcodes Jan 23, 2026
6fc15a5
feat: add clear-cache script and include rollup-plugin-preserve-use-c…
yamcodes Jan 23, 2026
4f7700a
Merge branch 'main' into 774-add-arkenvfumadocs-ui-theme
yamcodes Jan 23, 2026
a696ed2
feat: add design, proposal, tasks, and specs for publish-fumadocs-ui-…
yamcodes Jan 23, 2026
6e698c4
feat: add mdx script and update postinstall to check for fumadocs-ui …
yamcodes Jan 23, 2026
e9199df
refactor: update documentation and rename publish-fumadocs-ui-theme t…
yamcodes Jan 23, 2026
d8b8736
feat: enhance SVG icon handling in CodeBlock component with sanitization
yamcodes Jan 23, 2026
dc7ce49
feat: add initial release documentation for @arkenv/fumadocs-ui theme…
yamcodes Jan 23, 2026
9e081e9
feat: enhance heading component with improved accessibility and styli…
yamcodes Jan 23, 2026
568e097
feat: skip fumadocs-mdx execution if output already exists; remove ch…
yamcodes Jan 23, 2026
9d451bb
feat: simplify MDX output check by using directory existence instead …
yamcodes Jan 23, 2026
9003404
feat: update mdx task in turbo configuration to include additional in…
yamcodes Jan 23, 2026
b32c087
feat: update mdx task inputs to include source.config files
yamcodes Jan 23, 2026
79eec7a
Merge branch 'main' into 774-add-arkenvfumadocs-ui-theme
yamcodes Jan 23, 2026
9b793b6
Merge branch 'main' into 774-add-arkenvfumadocs-ui-theme
yamcodes Jan 23, 2026
9aec69e
docs(openspec): add agent instructions
yamcodes Jan 23, 2026
49bf32c
fix(heading): adjust heading icon positioning and size for better ali…
yamcodes Jan 23, 2026
1fbdeae
fix(theme): adjust heading icon positioning for better alignment
yamcodes Jan 23, 2026
8e02730
feat(nav): enhance external link icons in sidebar and navigation
yamcodes Jan 23, 2026
e2b65bf
fix(theme): update CodeBlock styles for improved appearance
yamcodes Jan 23, 2026
b352db4
docs(apply): update formatting for Guardrails, Steps, and Reference s…
yamcodes Jan 23, 2026
5d33421
fix(a11y): improve exclusion handling in assertNoA11yViolations function
yamcodes Jan 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/old-birds-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@arkenv/fumadocs-ui": patch
---

#### First release

`@arkenv/fumadocs-ui` provides a theme, and components, for `fumadocs-ui` to replicate the "ArkEnv" website look.
23 changes: 23 additions & 0 deletions .claude/commands/openspec/apply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: OpenSpec: Apply
description: Implement an approved OpenSpec change and keep tasks in sync.
category: OpenSpec
tags: [openspec, apply]
---
<!-- OPENSPEC:START -->
## Guardrails
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.

## Steps
Track these steps as TODOs and complete them one by one.
1. Read `changes/<id>/proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria.
2. Work through tasks sequentially, keeping edits minimal and focused on the requested change.
3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished.
4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality.
5. Reference `openspec list` or `openspec show <item>` when additional context is required.

## Reference
- Use `openspec show <id> --json --deltas-only` if you need additional context from the proposal while implementing.
<!-- OPENSPEC:END -->
27 changes: 27 additions & 0 deletions .claude/commands/openspec/archive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: OpenSpec: Archive
description: Archive a deployed OpenSpec change and update specs.
category: OpenSpec
tags: [openspec, archive]
---
<!-- OPENSPEC:START -->
**Guardrails**
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.

**Steps**
1. Determine the change ID to archive:
- If this prompt already includes a specific change ID (for example inside a `<ChangeId>` block populated by slash-command arguments), use that value after trimming whitespace.
- If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
- Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
- If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
2. Validate the change ID by running `openspec list` (or `openspec show <id>`) and stop if the change is missing, already archived, or otherwise not ready to archive.
3. Run `openspec archive <id> --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work).
4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`.
5. Validate with `openspec validate --strict` and inspect with `openspec show <id>` if anything looks off.

**Reference**
- Use `openspec list` to confirm change IDs before archiving.
- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off.
<!-- OPENSPEC:END -->
28 changes: 28 additions & 0 deletions .claude/commands/openspec/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: OpenSpec: Proposal
description: Scaffold a new OpenSpec change and validate strictly.
category: OpenSpec
tags: [openspec, change]
---
<!-- OPENSPEC:START -->
**Guardrails**
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files.
- Do not write any code during the proposal stage. Only create design documents (proposal.md, tasks.md, design.md, and spec deltas). Implementation happens in the apply stage after approval.

**Steps**
1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification.
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes/<id>/`.
3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing.
4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs.
5. Draft spec deltas in `changes/<id>/specs/<capability>/spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant.
6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work.
7. Validate with `openspec validate <id> --strict` and resolve every issue before sharing the proposal.

**Reference**
- Use `openspec show <id> --json --deltas-only` or `openspec show <spec> --type spec` to inspect details when validation fails.
- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones.
- Explore the codebase with `rg <keyword>`, `ls`, or direct file reads so proposals align with current implementation realities.
<!-- OPENSPEC:END -->
18 changes: 18 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!-- OPENSPEC:START -->
# OpenSpec Instructions

These instructions are for AI assistants working in this project.

Always open `@/openspec/AGENTS.md` when the request:
- Mentions planning or proposals (words like proposal, spec, change, plan)
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
- Sounds ambiguous and you need the authoritative spec before coding

Use `@/openspec/AGENTS.md` to learn:
- How to create and apply change proposals
- Spec format and conventions
- Project structure and guidelines

Keep this managed block so 'openspec update' can refresh the instructions.

<!-- OPENSPEC:END -->
2 changes: 1 addition & 1 deletion apps/www/app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ExternalLink } from "@arkenv/fumadocs-ui/components";
import type { Metadata } from "next";
import Image from "next/image";
import { AnnouncementBadge } from "~/components/announcement-badge";
Expand All @@ -9,7 +10,6 @@ import {
StarUsButton,
VideoDemo,
} from "~/components/page";
import { ExternalLink } from "~/components/ui/external-link";

export const metadata: Metadata = {
title: "ArkEnv",
Expand Down
5 changes: 2 additions & 3 deletions apps/www/app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AIActions } from "@arkenv/fumadocs-ui/components";
import { createRelativeLink } from "fumadocs-ui/mdx";
import {
DocsBody,
Expand All @@ -6,7 +7,6 @@ import {
DocsTitle,
} from "fumadocs-ui/page";
import { notFound } from "next/navigation";
import { LLMCopyButton, ViewOptions } from "~/components/page-actions";
import { source } from "~/lib/source";
import { getLinkTitleAndHref } from "~/lib/utils";
import { getMDXComponents } from "~/mdx-components";
Expand All @@ -26,8 +26,7 @@ export default async function Page(props: {
<DocsTitle className="mb-4">{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b pt-2 pb-6 mb-8 mt-4">
<LLMCopyButton markdownUrl={`${page.url}.mdx`} />
<ViewOptions
<AIActions
markdownUrl={`${page.url}.mdx`}
githubUrl={
getLinkTitleAndHref(
Expand Down
129 changes: 2 additions & 127 deletions apps/www/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import "tailwindcss";
@import "@arkenv/fumadocs-ui/css/theme.css";
@import "fumadocs-ui/css/ocean.css";
@import "fumadocs-ui/css/preset.css";
@import "./styles/theme/light.css";
Expand All @@ -11,133 +12,7 @@

@plugin "tailwindcss-animate";
@source "../node_modules/fumadocs-ui/dist/**/*.js";

/* External link icon variables */
@theme {
--icon-external: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23374151' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='7 7 17 7 17 17'%3E%3C/polyline%3E%3Cline x1='7' y1='17' x2='17' y2='7'%3E%3C/line%3E%3C/svg%3E");
--icon-external-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23d1d5db' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='7 7 17 7 17 17'%3E%3C/polyline%3E%3Cline x1='7' y1='17' x2='17' y2='7'%3E%3C/line%3E%3C/svg%3E");
}

/* Link underlines in documentation */
article
a:not(:has(img)):not(.fd-card):not([data-card]):not([class*="fd-"]):not(
[data-no-underline]
),
[data-external-link]:not(:has(img)):not(.fd-card):not([data-card]):not(
[class*="fd-"]
):not([data-no-underline]) {
text-decoration: none;
border-bottom: 1.5px solid currentColor;
padding-bottom: 1px;
transition: border-bottom-width 0.1s ease;
}

article
a:not(:has(img)):not(.fd-card):not([data-card]):not([class*="fd-"]):not(
[data-no-underline]
):hover,
[data-external-link]:not(:has(img)):not(.fd-card):not([data-card]):not(
[class*="fd-"]
):not([data-no-underline]):hover {
border-bottom-width: 2.5px;
}

/* External links in documentation */
article
a[rel*="noopener"][target="_blank"]:not(.fd-card):not([data-card]):not(
[data-no-arrow]
):not(:has(img)),
[data-external-link]:not(.fd-card):not([data-card]):not([data-no-arrow]):not(
:has(img)
) {
padding-right: 1.1em;
background-image: var(--icon-external);
background-repeat: no-repeat;
background-size: 0.9em;
background-position: center right;
}

.dark
article
a[rel*="noopener"][target="_blank"]:not(.fd-card):not([data-card]):not(
[data-no-arrow]
):not(:has(img)),
.dark
[data-external-link]:not(.fd-card):not([data-card]):not([data-no-arrow]):not(
:has(img)
) {
background-image: var(--icon-external-dark);
}

/* Sidebar & Nav: Automagic icons next to text for flex containers */
[data-radix-scroll-area-viewport]
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
),
#nd-nav
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
),
#nd-subnav
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
) {
background-image: none !important;
padding-right: 0 !important;
display: inline-flex;
align-items: center;
gap: 0 !important;
}

/* Sidebar specifically needs full flex for better link hit-areas */
[data-radix-scroll-area-viewport]
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
) {
display: flex;
}

[data-radix-scroll-area-viewport]
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
)::after,
#nd-nav
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
)::after,
#nd-subnav
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
)::after {
content: "";
width: 0.9em;
height: 0.9em;
margin-left: 0.1em;
background-image: var(--icon-external);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
flex-shrink: 0;
opacity: 0.5;
}

.dark
[data-radix-scroll-area-viewport]
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
)::after,
.dark
#nd-nav
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
)::after,
.dark
#nd-subnav
a[target="_blank"]:not([data-no-arrow]):not(:has(img)):not(
:has(> svg:only-child)
)::after {
background-image: var(--icon-external-dark);
}
@source "../node_modules/@arkenv/fumadocs-ui/dist/**/*.mjs";

button[data-search-full] {
--color-fd-secondary: hsl(0, 0%, 100%);
Expand Down
52 changes: 52 additions & 0 deletions apps/www/bin/mdx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env node

const { spawn, execSync } = require("node:child_process");

// Skip mdx types generation during tests - types aren't needed for running tests
if (process.env.SKIP_MDX === "true") {
process.exit(0);
}

// Get Node.js major version
const nodeVersion = process.version;
const majorVersion = Number.parseInt(nodeVersion.slice(1).split(".")[0], 10);

// Set NODE_OPTIONS based on Node.js version
if (majorVersion >= 25) {
// Node.js 25+ has Web Storage enabled by default, disable it to avoid localStorage conflicts
const existingOptions = process.env.NODE_OPTIONS || "";
const flag = "--no-webstorage";

// Only add the flag if it's not already present
if (!existingOptions.includes(flag)) {
process.env.NODE_OPTIONS = existingOptions
? `${existingOptions} ${flag}`
: flag;
}
}
// For Node.js 24 and below, leave existing NODE_OPTIONS unchanged

// Check if pnpm is available
try {
execSync("pnpm --version", { stdio: "ignore" });
} catch {
console.error("Error: pnpm is required but not found in PATH");
process.exit(1);
}

// Spawn fumadocs-mdx with the appropriate NODE_OPTIONS
// Use shell: true on Windows for command resolution, false on Unix for security
const child = spawn("pnpm", ["exec", "fumadocs-mdx"], {
stdio: "inherit",
shell: process.platform === "win32",
});

// Forward exit code
child.on("exit", (code) => {
process.exit(code ?? 1);
});

child.on("error", (error) => {
console.error("Failed to start fumadocs-mdx:", error);
process.exit(1);
});
10 changes: 10 additions & 0 deletions apps/www/bin/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ try {
process.exit(1);
}

// Avoid running when @arkenv/fumadocs-ui is not built yet (e.g. fresh CI install)
try {
require.resolve("@arkenv/fumadocs-ui/dist/utils/index.mjs");
} catch {
console.warn(
"Skipping fumadocs-mdx: @arkenv/fumadocs-ui is not built (dist missing).",
);
process.exit(0);
}

// Spawn fumadocs-mdx with the appropriate NODE_OPTIONS
// Use shell: true on Windows for command resolution, false on Unix for security
const child = spawn("pnpm", ["exec", "fumadocs-mdx"], {
Expand Down
4 changes: 2 additions & 2 deletions apps/www/components/announcement-badge.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type ArkenvUrl, isExternalUrl } from "@arkenv/fumadocs-ui/utils";
import { ArrowRight, ArrowUpRight } from "lucide-react";
import Link from "next/link";
import type { PropsWithChildren } from "react";
import { isExternalUrl, type Url } from "~/lib/utils/url";
import { NewBadge } from "./ui/new-badge";

export function AnnouncementBadge({
Expand All @@ -22,7 +22,7 @@ export function AnnouncementBadge({
/**
* The link to navigate to when clicking the badge.
*/
href: Url;
href: ArkenvUrl;
}>) {
return (
<Link
Expand Down
2 changes: 1 addition & 1 deletion apps/www/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { isExternalUrl } from "@arkenv/fumadocs-ui/utils";
import {
Card as CardComponent,
type CardProps,
} from "fumadocs-ui/components/card";
import { ArrowUpRight } from "lucide-react";
import { isExternalUrl } from "~/lib/utils/url";

export function Card({ title, ...props }: CardProps) {
const isExternal = isExternalUrl(props.href);
Expand Down
Loading
Loading