# Technical Specification

## Technology Choices

* Runtime: Node.js.
* CLI: Node executable exposed as `wikied`.
* Server: Express.
* Client: React.
* UI framework: Mantine (`@mantine/core`, `@mantine/hooks`) — AppShell, theme system, dark mode, component library.
* Build tool: Vite.
* Rich editor: MDXEditor.
* Markdown preview: React Markdown with `remark-gfm`.
* Diagrams: Mermaid.
* Icons: Lucide React.
* Tests: Vitest for unit/component/server tests and Playwright for end-to-end browser checks.
* ZIP extraction: fflate (browser and Node compatible, ~10 KB gzipped).

## Package Layout

```text
.
|-- docs/
|   |-- project/
|   |   |-- images/
|   |   |-- how-to/
|   |   |-- testing/
|   |   |-- architecture.md
|   |   |-- changelog.md
|   |   |-- design.md
|   |   |-- README.md
|   |   |-- requirements.md
|   |   `-- tech-spec.md
|   `-- superpowers/
|-- scripts/
|   |-- capture-doc-screenshots.mjs
|   `-- package-local.ps1
|-- src/
|   |-- cli/
|   |-- client/
|   |   |-- api/
|   |   |-- components/
|   |   |-- editor/
|   |   `-- App.tsx
|   |-- server/
|   |   |-- app.ts
|   |   |-- files.ts
|   |   |-- git.ts
|   |   `-- paths.ts
|   `-- shared/
|-- tests/
|   |-- client/
|   |-- e2e/
|   |-- fixtures/
|   |-- server/
|   `-- shared/
|-- package.json
|-- playwright.config.ts
|-- vite.config.ts
|-- vitest.config.ts
`-- tsconfig*.json
```

## CLI Behavior

Supported command forms:

```bash
wikied
wikied docs
wikied --git https://github.com/org/repo
wikied --git git@github.com:org/repo.git target-dir
node dist/cli/index.js docs --no-open --port 4173
npx --yes --package ./wikied-0.5.0.tgz wikied docs
wikied convert --input diagram.gliffy --output diagram.md
```

The CLI:

* Resolves the opened root path.
* Rejects missing or non-directory roots.
* Binds to `127.0.0.1`.
* Uses a random free port by default.
* Accepts `--port <number>`.
* Accepts `--no-open`.
* Accepts `--dev-client <url>` for development.
* Accepts `--git <url>`: validates the target directory is empty, then runs `git clone <url> .` (with stdio streamed to the terminal); exits with an error if the directory is non-empty or if the clone fails.
* Prints the local URL.
* Opens the browser only when settings allow it and `--no-open` is absent.

## REST API

All client paths are root-relative paths using `/` separators.

### `GET /api/health`

Returns root metadata including workspace state.

```json
{
  "ok": true,
  "app": "wikied",
  "rootName": "docs",
  "rootPath": "C:/example/project/docs",
  "isEmpty": false,
  "isGitRepo": true
}
```

`isEmpty` is `true` when the root directory has zero entries. `isGitRepo` is `true` when `root/.git` exists.

### `POST /api/clone`

Clones a git repository into the empty root.

Request:

```json
{ "url": "https://github.com/org/repo" }
```

* Returns `204 No Content` on success.
* Returns `400` if the URL is invalid or the root is not empty.
* Returns `500` with stderr as the error message body on clone failure.

URL validation accepts `http://`, `https://`, `git://`, `ssh://` schemes and SSH shorthand (`user@host:path`). Dash-prefixed values are always rejected.

### `GET /api/tree`

Returns the recursive file tree. Non-Markdown files are included with `editable: false`.

```json
{
  "items": [
    {
      "type": "file",
      "path": "README.md",
      "name": "README.md",
      "editable": true
    }
  ]
}
```

### `GET /api/files/content?path=README.md`

Reads a Markdown file and returns its revision token. UTF-16 LE BOM files are decoded automatically; the response body is always UTF-8 text.

```json
{
  "path": "README.md",
  "content": "# Hello",
  "revision": "mtime:size:hash",
  "mtimeMs": 1760000000000
}
```

### `PUT /api/files/content`

Saves a Markdown file if the submitted revision still matches disk.

Request:

```json
{
  "path": "README.md",
  "content": "# Updated",
  "revision": "mtime:size:hash"
}
```

Success:

```json
{
  "path": "README.md",
  "revision": "new-mtime:size:hash"
}
```

Conflict:

```json
{
  "error": {
    "code": "FILE_CONFLICT",
    "message": "The file changed on disk.",
    "latestContent": "# Disk version",
    "latestRevision": "newer-revision"
  }
}
```

### `POST /api/files`

Creates a Markdown file.

```json
{
  "path": "guides/new-guide.md",
  "content": "# New Guide\n"
}
```

### `PATCH /api/files/rename`

Renames a Markdown file.

```json
{
  "from": "guides/old.md",
  "to": "guides/new.md"
}
```

### `POST /api/assets`

Copies a local asset into the configured asset folder and returns both the workspace asset path and the Markdown URL relative to the active document.

```json
{
  "sourcePath": "C:/Users/example/Pictures/diagram.png",
  "sourceName": "diagram.png",
  "activeFilePath": "guides/setup.md",
  "assetFolder": "assets"
}
```

### `GET /api/git/status`

Returns the count of changed files in the workspace.

```json
{ "changedCount": 3 }
```

Returns `400` if the workspace is not a git repository.

### `POST /api/git/commit`

Stages all changes and commits with the provided message.

Request:

```json
{ "message": "docs: update guide" }
```

* Returns `204 No Content` on success.
* Returns `422` with `"Nothing to commit."` when the working tree is clean.
* Returns `400` if not a git repository.
* Returns `500` with stderr on failure.

### `POST /api/git/push`

Runs `git push` in the workspace root.

* Returns `204 No Content` on success.
* Returns `400` if not a git repository.
* Returns `500` with stderr on failure.

### `POST /api/git/pull`

Runs `git pull` in the workspace root.

* Returns `204 No Content` on success.
* Returns `400` if not a git repository.
* Returns `500` with stderr on failure.

### `GET /api/settings`

Returns user-level settings.

### `PUT /api/settings`

Persists user-level settings.

## Error Codes

The shared error codes are:

* `INVALID_PATH`
* `OUTSIDE_ROOT`
* `UNSUPPORTED_FILE`
* `FILE_NOT_FOUND`
* `FILE_EXISTS`
* `FILE_CONFLICT`
* `SETTINGS_ERROR`
* `ASSET_COPY_FAILED`
* `GIT_ERROR`

## Revision Strategy

Each file read returns a revision derived from:

* `mtimeMs`
* file size
* a short SHA-256 content hash

Saves compare the submitted revision with the current disk revision before writing.

The client App keeps `activeFile.revision` in sync after every successful save so that the multi-file dirty cache always holds a current revision when content is stashed on a file switch.

## File Encoding

`src/server/files.ts` reads all files as raw bytes before decoding:

1. If the first two bytes are `0xFF 0xFE` (UTF-16 LE BOM), the remaining bytes are decoded as `utf16le` and the BOM is stripped.
2. Otherwise the bytes are decoded as UTF-8.

Saved files are always written as UTF-8.

## Markdown Handling

Editable files are limited to `.md` and `.markdown`.

Rich mode uses MDXEditor. Preview mode renders Markdown with GFM and Mermaid support. Preview also supports built-in style presets by applying style-specific CSS classes to the rendered document.

## Testing Strategy

* Shared tests cover Markdown helpers.
* Server tests cover settings, paths, files, assets, git helpers, app routes, and startup.
* Client tests cover app loading, tree behavior, editor state, preview, recent files, API client behavior, and the Save and Git button components.
* E2E tests run through Playwright against a built app server.
* Screenshot generation uses Playwright through `scripts/capture-doc-screenshots.mjs`.
