Skip to content
Merged
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
40 changes: 37 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Auto-generated from all feature plans. Last updated: 2026-03-06

## Active Technologies

- Rust 2021 (backend), TypeScript 5.7 (frontend) + `ltk_modpkg` ^0.3.0, `ltk_mod_project` ^0.3.0 (Rust); `@tauri-apps/api` ^2.0.0, `zustand` ^5.0.0, `@tanstack/react-router` ^1.114.0 (frontend) (005-project-management)
- Filesystem only — mod project directories, `.forge/` scratch data, app-local JSON for recent projects (005-project-management)
- TypeScript 5.7 (frontend) + `@tanstack/react-form` (new), `@base-ui/react` (existing), `class-variance-authority` (existing), `tailwind-merge` (existing) (006-form-components)

- TypeScript 5.7, targeting ES2022+ (ESM) + @base-ui-components/react, tailwind-merge, clsx, Tailwind CSS v4, React 19 (004-component-library)
- N/A (no persistent storage) (004-component-library)

Expand Down Expand Up @@ -31,10 +35,40 @@ TypeScript 5.x, targeting ES2022+: Follow standard conventions

## Recent Changes

- 004-component-library: Added TypeScript 5.7, targeting ES2022+ (ESM) + @base-ui-components/react, tailwind-merge, clsx, Tailwind CSS v4, React 19
- 006-form-components: Added TypeScript 5.7 (frontend) + `@tanstack/react-form` (new), `@base-ui/react` (existing), `class-variance-authority` (existing), `tailwind-merge` (existing)
- 005-project-management: Added Rust 2021 (backend), TypeScript 5.7 (frontend) + `ltk_modpkg` ^0.3.0, `ltk_mod_project` ^0.3.0 (Rust); `@tauri-apps/api` ^2.0.0, `zustand` ^5.0.0, `@tanstack/react-router` ^1.114.0 (frontend)

- 003-commitlint-release-workflow: Added TypeScript 5.x (frontend), Rust 2021 edition (backend), YAML (CI workflows) + lefthook (git hooks), @commitlint/cli + @commitlint/config-conventional (commit validation), git-cliff (changelog generation via GitHub Action), Tauri v2 (build/release)
- 002-tauri-app-setup: Added TypeScript 5.x (frontend), Rust 2021 edition (backend) + Tauri v2, React 19, Vite 6, TanStack Router, Tailwind CSS v4, Zustand, tauri-plugin-{shell,dialog,fs,process}
- 004-component-library: Added TypeScript 5.7, targeting ES2022+ (ESM) + @base-ui-components/react, tailwind-merge, clsx, Tailwind CSS v4, React 19

<!-- MANUAL ADDITIONS START -->

## TanStack Documentation

**MANDATORY**: When working with TanStack packages (Router, Form, Query, Start, etc.), always use the `tanstack` CLI to fetch current documentation. Do NOT rely on training data — TanStack APIs change frequently.

### Key Commands

```bash
# Fetch a specific doc page
tanstack doc <library> <path> --json
# Examples:
tanstack doc router framework/react/guide/data-loading --json
tanstack doc form framework/react/overview --json
tanstack doc query framework/react/overview --json

# Search docs
tanstack search-docs "<query>" --library <id> --framework react --json
# Examples:
tanstack search-docs "loaders" --library router --framework react --json
tanstack search-docs "validation" --library form --framework react --json

# List available libraries
tanstack libraries --json

# Ecosystem partners (auth, database, etc.)
tanstack ecosystem --category <category> --json
```

Always use `--json` for deterministic parsing of output.

<!-- MANUAL ADDITIONS END -->
67 changes: 53 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 16 additions & 15 deletions apps/forge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,33 @@
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@ltk-forge/map-renderer": "workspace:*",
"@ltk-forge/mapgeo-types": "workspace:*",
"@ltk-forge/mapgeo-utils": "workspace:*",
"@ltk-forge/math": "workspace:*",
"@ltk-forge/theme": "workspace:*",
"@ltk-forge/ui": "workspace:*",
"@tanstack/react-form": "^1.28.4",
"@tanstack/react-router": "^1.114.0",
"zustand": "^5.0.0",
"@tauri-apps/api": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.0",
"@tauri-apps/plugin-fs": "^2.0.0",
"@tauri-apps/plugin-process": "^2.0.0",
"@ltk-forge/math": "workspace:*",
"@ltk-forge/mapgeo-types": "workspace:*",
"@ltk-forge/mapgeo-utils": "workspace:*",
"@ltk-forge/map-renderer": "workspace:*",
"@ltk-forge/theme": "workspace:*",
"@ltk-forge/ui": "workspace:*"
"@tauri-apps/plugin-shell": "^2.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zustand": "^5.0.0"
},
"devDependencies": {
"@ltk-forge/eslint-config": "workspace:*",
"@ltk-forge/tsconfig": "workspace:*",
"@tailwindcss/vite": "^4.0.0",
"@tanstack/router-plugin": "^1.114.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.0.0",
"vite": "^6.0.0",
"@tailwindcss/vite": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.0",
"@tanstack/router-plugin": "^1.114.0",
"@ltk-forge/eslint-config": "workspace:*",
"@ltk-forge/tsconfig": "workspace:*"
"vite": "^6.0.0"
}
}
34 changes: 34 additions & 0 deletions apps/forge/src/components/fields/CheckboxField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useFieldContext } from "../../lib/form-context";
import { FieldWrapper, Checkbox } from "@ltk-forge/ui";

export interface CheckboxFieldProps {
label: string;
description?: string;
}

export function CheckboxField({ label, description }: CheckboxFieldProps) {
const field = useFieldContext<boolean>();

const errors = field.state.meta.errors;
const error =
field.state.meta.isTouched && errors.length > 0
? errors.map((e) => (typeof e === "string" ? e : String(e))).join(", ")
: undefined;

return (
<FieldWrapper description={description} error={error}>
<div className="flex items-center gap-2">
<Checkbox
checked={field.state.value ?? false}
onCheckedChange={(checked) => {
field.handleChange(checked as boolean);
field.handleBlur();
}}
/>
<label className="text-[length:var(--font-size-sm)] text-[var(--color-text-primary)] select-none cursor-pointer">
{label}
</label>
</div>
</FieldWrapper>
);
}
59 changes: 59 additions & 0 deletions apps/forge/src/components/fields/SelectField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useFieldContext } from "../../lib/form-context";
import { FieldWrapper, Select } from "@ltk-forge/ui";

export interface SelectFieldProps {
label?: string;
description?: string;
required?: boolean;
placeholder?: string;
options: Array<{ value: string; label: string }>;
}

export function SelectField({
label,
description,
required,
placeholder,
options,
}: SelectFieldProps) {
const field = useFieldContext<string>();

const errors = field.state.meta.errors;
const error =
field.state.meta.isTouched && errors.length > 0
? errors.map((e) => (typeof e === "string" ? e : String(e))).join(", ")
: undefined;

return (
<FieldWrapper
label={label}
description={description}
error={error}
required={required}
>
<Select.Root
value={field.state.value ?? ""}
onValueChange={(val) => {
field.handleChange(val ?? "");
field.handleBlur();
}}
>
<Select.Trigger>
<Select.Value placeholder={placeholder} />
<Select.Icon />
</Select.Trigger>
<Select.Portal>
<Select.Positioner>
<Select.Popup>
{options.map((opt) => (
<Select.Item key={opt.value} value={opt.value}>
<Select.ItemText>{opt.label}</Select.ItemText>
</Select.Item>
))}
</Select.Popup>
</Select.Positioner>
</Select.Portal>
</Select.Root>
</FieldWrapper>
);
}
28 changes: 28 additions & 0 deletions apps/forge/src/components/fields/SubmitButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useFormContext } from "../../lib/form-context";
import { Button } from "@ltk-forge/ui";
import type { ButtonProps } from "@ltk-forge/ui";

export interface SubmitButtonProps extends Omit<
ButtonProps,
"type" | "disabled"
> {
loadingText?: string;
}

export function SubmitButton({
children = "Submit",
loadingText = "Submitting...",
...props
}: SubmitButtonProps) {
const form = useFormContext();

return (
<form.Subscribe selector={(state) => [state.canSubmit, state.isSubmitting]}>
{([canSubmit, isSubmitting]) => (
<Button type="submit" disabled={!canSubmit || isSubmitting} {...props}>
{isSubmitting ? loadingText : children}
</Button>
)}
</form.Subscribe>
);
}
Loading
Loading