Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
node_modules/
.next/
*.tsbuildinfo
dist/
*.vsix
.vscode-test/
Expand All @@ -13,6 +15,7 @@ dist-e2e/
.playwright-cli/
playwright-report/
test-results/
/output/
blob-report/
packages/extension/tests/playwright-vscode/generated/
packages/extension/tests/playwright-vscode/generated-ir/
Expand Down
33 changes: 33 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,28 @@ _Avoid_: Required plugin
The Plugin Package bundled with CodeGraphy, enabled by default for new CodeGraphy Workspaces, and still toggleable like any other Plugin. Core Plugin Activity State treats Markdown as enabled for a fresh workspace before settings are materialized, so CodeGraphy Interfaces can show its toggle as enabled without creating `.codegraphy/settings.json`. When workspace settings are created, such as during first Indexing or an explicit plugin toggle, Markdown is written as `enabled: true` unless the user disables it; disabling persists `enabled: false`. The Markdown Plugin exists because Tree-sitter Analysis does not provide CodeGraphy's markdown relationships; disabling it fully unloads the plugin runtime and leaves only static metadata for the enable toggle.
_Avoid_: External markdown extension, special-case markdown runtime

### Commercial Layer

**CodeGraphy Account**:
A signed-in website account that starts free and can later hold paid Pro package entitlements.
_Avoid_: Pro Account when the account has no paid package

**Free Account**:
A CodeGraphy Account without an active paid Pro package entitlement.
_Avoid_: Free CodeGraphy use when the user has not signed up

**CodeGraphy Pro**:
The paid commercial tier for CodeGraphy Accounts. A user becomes Pro by paying for at least one Pro Package.
_Avoid_: CodeGraphy Account, Free Account

**Pro Package**:
A paid package attached to a CodeGraphy Account as a Pro entitlement.
_Avoid_: Workspace Package, Plugin Package

**Organize**:
The first planned Pro Package for keeping, switching, arranging, and presenting useful CodeGraphy graph setups.
_Avoid_: CodeGraphy Pro when referring only to this package

### Settings And Styling

**Setting**:
Expand Down Expand Up @@ -764,6 +786,10 @@ _Avoid_: Graph export
- A **Plugin Package** is the packaging route for third-party plugins.
- **Built-in Plugins** in this monorepo are examples and fast-development plugins, not required dependencies unless explicitly installed or bundled by the Core Package.
- The **Markdown Plugin** is installed with `@codegraphy-dev/core` and enabled by default for new CodeGraphy Workspaces, but users can still toggle it off.
- Free CodeGraphy use starts from the **VS Code Extension** and does not require a **CodeGraphy Account**.
- A user can create a **Free Account** on the website before buying any **Pro Package**.
- A **CodeGraphy Account** becomes **CodeGraphy Pro** when it has at least one active paid **Pro Package** entitlement.
- **Organize** is a **Pro Package** inside **CodeGraphy Pro**, not a synonym for all signed-in account behavior.
- A **Settings Control** changes a **Setting**; it is not a separate persisted concept.
- **Settings** are saved workspace-locally under `.codegraphy/settings.json` so graph preferences survive between sessions.
- **Graph Scope**, **Filter Setting**, **Display Setting**, **Verbose Diagnostics**, **Favorite**, and **Legend Entry Toggle** are settings because they are saved between sessions.
Expand Down Expand Up @@ -857,6 +883,12 @@ _Avoid_: Graph export
>
> **Dev:** "If I turn off the Godot `*.gd` Legend Entry, do GDScript files disappear?"
> **Domain expert:** "No. The **Legend Entry Toggle** only disables that styling, so matching nodes fall back to lower-priority styling."
>
> **Dev:** "Does installing CodeGraphy require a Pro account?"
> **Domain expert:** "No. Free CodeGraphy starts from the **VS Code Extension** and does not require a **CodeGraphy Account**. The website can still let someone create a **Free Account**."
>
> **Dev:** "Is Organize the same thing as Pro?"
> **Domain expert:** "No. **CodeGraphy Pro** is the paid tier. **Organize** is the first **Pro Package** inside that tier."

## Flagged ambiguities

Expand All @@ -872,6 +904,7 @@ _Avoid_: Graph export
- "collapse dependents" was ambiguous; resolved: **Collapse** absorbs downstream relationship nodes, not upstream nodes.
- Shared downstream relationship targets stay visible when they are still related to by visible nodes outside the collapsed subgraph.
- When a shared relationship target stays visible, the downstream path to it stays visible as a **Boundary Path**.
- "Bookmark" conflicts with **Favorite** if it only means marking a node; unresolved: website and Organize language still need a canonical term for reusable saved graph setups.
- Collapse behavior is not renderer-owned; resolved: CodeGraphy owns **Collapse Projection**, it runs after the **Visible Graph** exists, and the force graph renderer displays the resulting graph.
- Do not introduce "Collapsed Graph" as a separate pipeline term for now; resolved: the user still sees the **Visible Graph**, updated by **Collapse Projection**.
- "filter" and "collapse" both reduce **Visible Graph** detail but are not synonyms; resolved: **Filter** means persistent include/exclude criteria, while **Collapse** means summarize relevant hidden detail.
Expand Down
14 changes: 14 additions & 0 deletions apps/web/app/_site/brand.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Link from 'next/link';

export function Brand({
href = '/',
}: {
href?: string;
}): React.ReactElement {
return (
<Link className="inline-flex items-center gap-2 font-semibold" href={href}>
<img alt="" className="h-8 w-8" src="/codegraphy-icon.svg" />
<span>CodeGraphy</span>
</Link>
);
}
10 changes: 10 additions & 0 deletions apps/web/app/_site/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function SiteFooter(): React.ReactElement {
return (
<footer className="border-t border-border/70 px-5 py-8 text-sm text-muted-foreground md:px-8" data-force-field-section="true">
<div className="force-field-content mx-auto flex max-w-7xl flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<span>CodeGraphy</span>
<span>Visualize your code's connections.</span>
</div>
</footer>
);
}
39 changes: 39 additions & 0 deletions apps/web/app/_site/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Link from 'next/link';
import { LogOut } from 'lucide-react';
import { Button } from '../_ui/button';
import { VsCodeIcon } from '../_ui/icons';
import { Brand } from './brand';

export function SiteHeader({
isSignedIn = false,
}: {
isSignedIn?: boolean;
}): React.ReactElement {
return (
<header className="sticky top-0 z-20 border-b border-border/80 bg-background">
<div className="mx-auto flex min-h-16 max-w-7xl items-center justify-between gap-4 px-5 md:px-8">
<Brand />
<div className="flex items-center justify-end gap-2">
{isSignedIn ? (
<Button asChild className="px-3 sm:px-4" variant="ghost">
<Link aria-label="Sign out" href="/login">
<LogOut size={16} />
<span className="hidden sm:inline">Sign out</span>
</Link>
</Button>
) : (
<Button asChild className="hidden sm:inline-flex" variant="ghost">
<Link href="/login">Sign in</Link>
</Button>
)}
<Button asChild>
<a href="https://marketplace.visualstudio.com/items?itemName=codegraphy.codegraphy">
<VsCodeIcon />
Install
</a>
</Button>
</div>
</div>
</header>
);
}
47 changes: 47 additions & 0 deletions apps/web/app/_ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from './cn';

const buttonVariants = cva(
'inline-flex min-h-10 items-center justify-center gap-2 rounded-md text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
outline: 'border border-input bg-background text-foreground hover:bg-foreground/5',
ghost: 'hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
},
size: {
default: 'px-4 py-2',
sm: 'px-3 py-1.5',
lg: 'px-5 py-3 text-base',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

export function Button({
asChild = false,
className,
size,
variant,
...props
}: ButtonProps): React.ReactElement {
const Comp = asChild ? Slot : 'button';

return <Comp className={cn(buttonVariants({ variant, size, className }))} {...props} />;
}

export { buttonVariants };
14 changes: 14 additions & 0 deletions apps/web/app/_ui/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react';
import { cn } from './cn';

export function Card({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
return (
<div
className={cn('rounded-md border border-border bg-card text-card-foreground shadow-sm', className)}
{...props}
/>
);
}
6 changes: 6 additions & 0 deletions apps/web/app/_ui/cn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
56 changes: 56 additions & 0 deletions apps/web/app/_ui/icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export function GoogleIcon({
className = 'h-4 w-4',
}: {
className?: string;
}): React.ReactElement {
return (
<svg aria-hidden="true" className={className} viewBox="0 0 24 24">
<path
d="M21.54 12.23c0-.78-.07-1.53-.2-2.23H12v4.22h5.34a4.56 4.56 0 0 1-1.98 2.99v2.48h3.2c1.87-1.72 2.98-4.26 2.98-7.46Z"
fill="#4285F4"
/>
<path
d="M12 22c2.7 0 4.96-.9 6.61-2.43l-3.2-2.48c-.89.6-2.03.95-3.41.95-2.61 0-4.82-1.76-5.61-4.13H3.08v2.56A9.99 9.99 0 0 0 12 22Z"
fill="#34A853"
/>
<path
d="M6.39 13.91A6 6 0 0 1 6.07 12c0-.66.11-1.3.32-1.91V7.53H3.08A9.99 9.99 0 0 0 2 12c0 1.61.39 3.14 1.08 4.47l3.31-2.56Z"
fill="#FBBC05"
/>
<path
d="M12 5.96c1.47 0 2.78.5 3.82 1.49l2.86-2.86C16.95 2.98 14.7 2 12 2a9.99 9.99 0 0 0-8.92 5.53l3.31 2.56C7.18 7.72 9.39 5.96 12 5.96Z"
fill="#EA4335"
/>
</svg>
);
}

export function GitHubIcon({
className = 'h-4 w-4',
}: {
className?: string;
}): React.ReactElement {
return (
<svg aria-hidden="true" className={className} viewBox="0 0 24 24">
<path
d="M12 .5a11.5 11.5 0 0 0-3.64 22.41c.58.11.79-.25.79-.56v-2.14c-3.22.7-3.9-1.37-3.9-1.37-.53-1.35-1.3-1.71-1.3-1.71-1.06-.72.08-.71.08-.71 1.17.08 1.79 1.2 1.79 1.2 1.04 1.78 2.73 1.27 3.4.97.1-.75.41-1.27.74-1.56-2.57-.29-5.28-1.29-5.28-5.73 0-1.27.45-2.3 1.2-3.11-.12-.29-.52-1.47.11-3.07 0 0 .98-.31 3.19 1.19a11.04 11.04 0 0 1 5.82 0c2.21-1.5 3.18-1.19 3.18-1.19.64 1.6.24 2.78.12 3.07.75.81 1.2 1.84 1.2 3.11 0 4.45-2.71 5.43-5.29 5.72.42.36.8 1.08.8 2.18v3.24c0 .31.21.68.8.56A11.5 11.5 0 0 0 12 .5Z"
fill="currentColor"
/>
</svg>
);
}

export function VsCodeIcon({
className = 'h-4 w-4',
}: {
className?: string;
}): React.ReactElement {
return (
<svg aria-hidden="true" className={className} viewBox="0 0 24 24">
<path
d="M17.6 3.2 8.8 11.1 3.4 7 2 8.2l4.7 3.8L2 15.8 3.4 17l5.4-4.1 8.8 7.9 4.4-1.8V5l-4.4-1.8Zm-.2 5.4v6.8L12.1 12l5.3-3.4Z"
fill="currentColor"
/>
</svg>
);
}
5 changes: 5 additions & 0 deletions apps/web/app/account/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AccountView } from './view';

export default function AccountPage(): React.ReactElement {
return <AccountView />;
}
21 changes: 21 additions & 0 deletions apps/web/app/account/view.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { AccountView } from './view';

describe('CodeGraphy website account page', () => {
it('shows the account email and active Pro package status', () => {
render(<AccountView />);

expect(screen.getByText('maya@codegraphy.dev')).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Private plugins' })).toBeInTheDocument();
expect(screen.getByText('Organize')).toBeInTheDocument();
expect(
screen.getByText('Sections, pinned nodes, saved setups, and advanced exports.'),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Customer portal coming soon' })).toBeDisabled();
expect(screen.getByRole('link', { name: 'Sign out' })).toHaveAttribute('href', '/login');
expect(screen.queryByRole('link', { name: 'Sign in' })).not.toBeInTheDocument();
expect(screen.queryByText('Free account')).not.toBeInTheDocument();
expect(screen.queryByText('Private plugins enabled')).not.toBeInTheDocument();
});
});
89 changes: 89 additions & 0 deletions apps/web/app/account/view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { CreditCard, PackageCheck, UserRound } from 'lucide-react';
import { Button } from '../_ui/button';
import { Card } from '../_ui/card';
import { SiteHeader } from '../_site/header';

export function AccountView(): React.ReactElement {
return (
<>
<SiteHeader isSignedIn />
<main className="mx-auto max-w-5xl px-5 py-10 md:px-8 md:py-12">
<div className="mb-8">
<p className="section-kicker-blue mb-3 text-xs font-black uppercase">
CodeGraphy account
</p>
<h1 className="text-4xl font-black sm:text-5xl">Account</h1>
<p className="mt-3 max-w-2xl text-base leading-7 text-muted-foreground">
Manage your account and private plugin subscriptions.
</p>
</div>

<section className="grid gap-4 md:grid-cols-2">
<Card className="min-w-0 bg-card/90 p-6">
<div className="mb-6 flex items-center gap-3">
<div className="relative flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-full bg-[hsl(var(--brand-blue))]">
<div className="absolute -left-4 top-4 h-6 w-16 rounded-full border border-white/38" />
<div className="absolute -right-4 bottom-2 h-6 w-16 rounded-full border border-[hsl(var(--brand-orange)/0.72)]" />
<UserRound className="relative text-white" size={21} />
</div>
<div>
<h2 className="text-xl font-black">Profile</h2>
<p className="text-sm text-muted-foreground">Signed in through your account provider.</p>
</div>
</div>
<dl className="grid gap-2">
<dt className="text-xs font-black uppercase text-muted-foreground">
Email
</dt>
<dd className="break-words text-lg font-black">maya@codegraphy.dev</dd>
</dl>
</Card>

<Card className="min-w-0 bg-card/90 p-6">
<div className="mb-6 flex items-center gap-3">
<span className="flex h-10 w-10 items-center justify-center rounded-md bg-[hsl(var(--brand-blue)/0.11)] text-[hsl(var(--brand-blue))]">
<CreditCard size={20} />
</span>
<div>
<h2 className="text-xl font-black">Subscription</h2>
<p className="text-sm text-muted-foreground">Billing management will connect through Stripe.</p>
</div>
</div>
<Button disabled variant="secondary">
<CreditCard size={16} />
Customer portal coming soon
</Button>
</Card>
</section>

<Card className="mt-4 min-w-0 bg-card/90 p-6">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-3">
<span className="flex h-10 w-10 items-center justify-center rounded-md bg-[hsl(var(--brand-blue)/0.11)] text-[hsl(var(--brand-blue))]">
<PackageCheck size={20} />
</span>
<div>
<h2 className="text-xl font-black">Private plugins</h2>
<p className="text-sm text-muted-foreground">Optional paid plugins attached to this account.</p>
</div>
</div>
</div>

<div className="mt-6 rounded-md border border-border/80 bg-background/55 p-4">
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div>
<p className="text-lg font-black">Organize</p>
<p className="mt-1 leading-6 text-muted-foreground">
Sections, pinned nodes, saved setups, and advanced exports.
</p>
</div>
<span className="w-fit whitespace-nowrap rounded-full bg-[hsl(var(--brand-orange)/0.12)] px-3 py-1 text-sm font-bold text-[hsl(var(--brand-orange))]">
Active
</span>
</div>
</div>
</Card>
</main>
</>
);
}
Loading