diff --git a/.changeset/shy-badgers-drum.md b/.changeset/shy-badgers-drum.md
new file mode 100644
index 00000000..686af968
--- /dev/null
+++ b/.changeset/shy-badgers-drum.md
@@ -0,0 +1,5 @@
+---
+'@elek-io/client': patch
+---
+
+Using new provider for breadcrumbs to allow for static and dynamic page labels in conjuction with Tanstack router and query.
diff --git a/.changeset/violet-parks-move.md b/.changeset/violet-parks-move.md
new file mode 100644
index 00000000..45ccde9e
--- /dev/null
+++ b/.changeset/violet-parks-move.md
@@ -0,0 +1,5 @@
+---
+'@elek-io/client': patch
+---
+
+Using Tanstack query as a wrapper around IPC calls for cache handling. Improved perceived performance since we do not wait for data to load before rendering pages and components.
diff --git a/documentation/README.md b/documentation/README.md
new file mode 100644
index 00000000..583a50b0
--- /dev/null
+++ b/documentation/README.md
@@ -0,0 +1,44 @@
+# Developer Documentation
+
+Welcome to the elek.io Client developer documentation. This guide will help you understand the codebase architecture and development patterns.
+
+> [!NOTE]
+> If your main interest is using Projects content inside your own applications, you can skip to the [Consuming Content Locally](./consuming-content-locally.md) section.
+
+## Prerequisites
+
+- Familiarity with TypeScript and React
+- Basic understanding of Electron architecture
+- Knowledge of React Hook Form and TanStack Query (helpful but not required)
+- Understanding of Git and version control concepts
+
+## Getting Started
+
+**Start with [Overview](./overview.md)** to understand the application architecture, security model, and how the different processes communicate.
+
+Then proceed to the specific topics based on your interests or contribution goals:
+
+- **[Loading and Updating Data](./renderer/loading-and-updating-data.md)** - TanStack Query patterns for data fetching and mutations
+- **[Dynamic Form Generation](./renderer/dynamic-form-field-generation.md)** - How user-defined forms work with field definitions
+- **[Breadcrumb Navigation](./renderer/breadcrumb-navigation.md)** - Route-based breadcrumb system for hierarchical navigation
+
+## Contributing
+
+When updating these docs:
+
+- Keep code examples up-to-date with actual implementation
+- Include file references with line numbers where relevant
+- Add practical examples for complex concepts
+- Update the "Last Updated" date at the bottom of each document
+
+## Additional Resources
+
+- [Electron Documentation](https://www.electronjs.org/docs/latest)
+- [TanStack Query Documentation](https://tanstack.com/query/latest)
+- [React Hook Form Documentation](https://react-hook-form.com/get-started)
+- [shadcn/ui Documentation](https://ui.shadcn.com/docs)
+- [@elek-io/core Repository](https://github.com/elek-io/core)
+
+---
+
+**Last Updated:** 2025-12-04
diff --git a/documentation/consuming-content-locally.md b/documentation/consuming-content-locally.md
index 7030b8cb..f39c6adc 100644
--- a/documentation/consuming-content-locally.md
+++ b/documentation/consuming-content-locally.md
@@ -1,90 +1,164 @@
-# Consuming content locally
+# Consuming Content Locally
-Consuming created content is essential to actually benefit from a CMS. elek.io Client and Core therefore provide multiple ways of doing so.
+## Overview
-**Use Cases**:
+Once you've created content in elek.io, you probably want to consume it in your applications. elek.io Client and Core provide multiple ways to access your Project data from external applications.
-- Static site generators (Next.js, Gatsby, Hugo)
-- Custom build tools
-- Really anything you can think of
+**Common Use Cases**:
-## Export Project data to JSON file
+- **Static Site Generators**: Fetch content for Next.js, Gatsby, Astro, Hugo, etc.
+- **Custom Build Tools**: Integrate content into your CI/CD pipeline
+- **Mobile Apps**: Access content via the local API from React Native, Flutter, etc.
+- **Desktop Applications**: Read content from other Electron or native apps
+- **Documentation Sites**: Generate docs from your elek.io content
+- **Data Analysis**: Export content for analytics or reporting tools
-elek.io Core can be installed globally via your package manager of choice:
+## Export Project Data to JSON Files
+
+### Installation
+
+First, install elek.io Core globally via your package manager:
```bash
npm install -g @elek-io/core
```
-With the included CLI you can then run one of the following commands inside e.g. your websites source code directory:
+### Export Commands
+
+The built-in CLI allows you to export Project data to JSON files. Run these commands e.g. inside your website's source code directory (or any directory where you want the exported data). Alternatively integrate them into your build scripts or CI/CD pipelines to ensure your application always has the latest content:
```bash
# Export all Projects content to .elek.io/projects.json
elek export
-# Export one Project to .elek.io/projects.json
-elek export ./.elek.io [projectId]
+# Export one Project by ID to .elek.io/projects.json
+elek export ./.elek.io 467e57ea-e04a-44a7-b34b-684ed3ba6f49
# Export one Project to .elek.io/project-${id}.json
-elek export ./.elek.io [projectId]
+elek export ./.elek.io 467e57ea-e04a-44a7-b34b-684ed3ba6f49
# Export multiple Projects into separate .elek.io/project-${id}.json files
-elek export ./.elek.io [projectId1,projectId2] --separate
+elek export ./.elek.io 467e57ea-e04a-44a7-b34b-684ed3ba6f49,bcffed17-4946-4336-b420-3974d9c94a43 --separate
```
-Add the `-w` or `--watch` option to any of these commands to automatically re-export whenever a Project changes.
+> [!NOTE]
+> Replace `467e57ea-e04a-44a7-b34b-684ed3ba6f49` with your actual Project ID. You can find Project IDs in the elek.io Client UI or in your Project's `.elek.io` directory.
+
+### Watch Mode
-Then use the content inside these JSON file(s) to populate your website or application:
+Add the `-w` or `--watch` flag to automatically re-export whenever a Project changes:
+
+```bash
+elek export ./.elek.io 467e57ea-e04a-44a7-b34b-684ed3ba6f49 --watch
+```
+
+This is particularly useful during development when you're actively updating content.
+
+### Using Exported JSON
+
+Once exported, you can import and use the JSON data in your application:
```typescript
import * as projects from './.elek.io/projects.json' with { type: 'json' };
-const entries = projects['...'].collections['...'].entries;
+// Access a specific Project's Collections
+const myProject = projects['467e57ea-e04a-44a7-b34b-684ed3ba6f49'];
+const blogPosts = myProject.collections['blog-posts'].entries;
-// Use the Entries
+// Use the Entries in your app
+blogPosts.forEach((post) => {
+ console.log(post.title, post.content);
+});
```
## Local API
-Enable the local API in your User Profile of elek.io Client → "Enable Local API Server". Then you can visit `http://localhost:31310/` (or your specified port) to view a rendered OpenAPI documentation and execute queries.
+elek.io Core also provides a local REST API server that runs on your machine, allowing applications to query your elek.io Projects for content.
+
+### Enabling the Local API
+
+**Via elek.io Client**
+
+1. Open elek.io Client
+2. Navigate to **User Profile** (top-right menu)
+3. Scroll to the **Local API** section
+4. Toggle **"Enable Local API Server"**
+5. Optionally change the port (default: `31310`)
+6. Click **Save**
+
+**Via elek.io Core**
+
+You can also start the local API server directly using elek.io Core CLI:
+
+```bash
+elek api:start 31310
+```
+
+Or programmatically in your Node.js application:
+
+```typescript
+import ElekIoCore from '@elek-io/core';
+
+const core = new ElekIoCore();
+core.api.start(31310);
+```
+
+Once enabled, you can visit `http://localhost:31310/` (or your specified port) to view the OpenAPI documentation and execute test queries.
+
+> [!WARNING]
+> Make sure the port you choose is not already in use by another application. If the API fails to start, try a different port.
-### Generate TS/JS API Client
+### Generate TypeScript/JavaScript API Client
-Of course you could write an API Client yourself but for TypeScript / JavaScript applications we provide a generated API Client with validation.
+While you can write an API client manually, elek.io provides a code generator that creates a type-safe client with built-in validation for TypeScript and JavaScript applications.
-Install elek.io Core globally via your package manager of choice:
+#### Generate the Client
+
+If you haven't already, install elek.io Core globally:
```bash
npm install -g @elek-io/core
```
-Then use the CLI to generate a TS or JS API Client with one of the following commands:
+Then generate the API client:
```bash
-# Generate TS API Client with default options in ./.elek.io/client.ts
+# Generate TypeScript API Client with default options in ./.elek.io/client.ts
elek generate:client
-# Generate JS API Client with ESM and target ES2020 in ./.elek.io/client.ts
+# Generate JavaScript API Client with ESM and target ES2020
elek generate:client ./.elek.io js esm es2020
```
-Then import and use the generated API Client like:
+#### Using the Generated Client
+
+Import and use the generated API client in your application:
```typescript
import { apiClient } from './.elek.io/client.js';
const client = apiClient({
baseUrl: 'http://localhost:31310',
- apiKey: 'abc123', // Not used for now
+ apiKey: 'abc123', // Not currently used, reserved for future authentication
});
-const entries =
- await client.content.v1.projects['...'].collections['...'].entries.list();
+// Fetch all Entries from a specific Collection
+const blogPosts =
+ await client.content.v1.projects[
+ '467e57ea-e04a-44a7-b34b-684ed3ba6f49'
+ ].collections['blog-posts'].entries.list();
+
+// Access individual Entries
+blogPosts.forEach((post) => {
+ console.log(post.title, post.publishedAt);
+});
```
-### Generate API Client for other languages
+### Generate API Client for Other Languages
-For languages other than TS/JS you can either write an API Client yourself, or use an OpenAPI generator like:
+For languages other than TypeScript/JavaScript, you can use any OpenAPI-compatible code generator. The local API exposes its full schema at `/openapi.json`.
+
+#### Example: Generate a Java Client
```bash
npx openapi-generator-cli generate \
@@ -92,3 +166,47 @@ npx openapi-generator-cli generate \
-g java \
-o ./.elek.io/client
```
+
+#### Example: Generate a Python Client
+
+```bash
+npx openapi-generator-cli generate \
+ -i http://localhost:31310/openapi.json \
+ -g python \
+ -o ./.elek.io/client
+```
+
+> [!NOTE]
+> Make sure the local API is running before generating the client, as the generator needs to fetch the OpenAPI schema.
+
+For more generators and options, see the [OpenAPI Generator documentation](https://openapi-generator.tech/docs/generators).
+
+## Troubleshooting
+
+### Port Already in Use
+
+If you see an error that the port is already in use:
+
+1. Check which application is using the port: `lsof -i :31310` (macOS/Linux) or `netstat -ano | findstr :31310` (Windows)
+2. Either stop that application or choose a different port
+
+### API Not Starting
+
+If the local API fails to start:
+
+1. Check the elek.io Client logs for error messages
+2. Ensure you have the latest version of elek.io Client and Core
+3. Try restarting elek.io Client
+4. Verify no firewall is blocking the port
+
+### Generated Client Not Working
+
+If the generated API client has type errors or runtime issues:
+
+1. Regenerate the client with the latest schema
+2. Ensure the local API is running and accessible
+3. Check that your Project IDs and Collection IDs are correct
+
+---
+
+**Last Updated:** 2025-11-18
diff --git a/documentation/overview.md b/documentation/overview.md
index 91a383c8..fb29f779 100644
--- a/documentation/overview.md
+++ b/documentation/overview.md
@@ -1,6 +1,15 @@
# Overview
-elek.io Client is built on [Electron](https://www.electronjs.org/), to create a cross-platform desktop application with web technologies (TypeScript, React). The architecture consists of three main layers: the Main Process, the Preload Script, and the Renderer Process.
+## Introduction
+
+elek.io Client is built on [Electron](https://www.electronjs.org/) to create a cross-platform desktop application with web technologies (TypeScript, React).
+
+> [!NOTE]
+> By choosing Electron, we keep the languages (everything is mainly TypeScript) used throughout our repositories to a minimum and the knowledge barrier for potential contributors low. Although applications build e.g. with [Tauri](https://tauri.app/) do have a smaller bundle size and memory footprint, adding another language (Rust) would increase complexity for contributors by alot.
+
+## Architecture
+
+Electron applications consist of three main layers: the Main Process, the Preload Script, and the Renderer Process.
```
┌─────────────────────────────────────────────────────────┐
@@ -24,7 +33,7 @@ elek.io Client is built on [Electron](https://www.electronjs.org/), to create a
While elek.io Client provides the user interface in a desktop application, all core functionalities related to file I/O, content handling, Git operations, local read-only API hosting and CLI usage are encapsulated in the separate [@elek-io/core](https://github.com/elek-io/core) library.
-It is used by the Main Process, since only it has access to the filesystem and Node.js APIs - the Renderer Process is sandboxed for security reasons.
+@elek-io/core is used by the Main Process, since only it has access to the filesystem and Node.js APIs - the Renderer Process is sandboxed for security reasons.
Therefore the Renderer Process communicates with the Main Process via IPC (Inter-Process Communication) to request operations via @elek-io/core.
@@ -61,11 +70,11 @@ contextBridge.exposeInMainWorld('ipc', {
// Renderer process uses the IPC API (src/renderer/)
const project = await window.ipc.core.projects.create({
name: 'My Project',
- description: 'A new project',
+ description: 'A new Project',
});
```
-50+ IPC Channels are available and organized by namespace.
+35+ IPC Channels are available and organized by namespace.
### Project Structure
@@ -79,10 +88,12 @@ client/
│ └── renderer/ # React frontend
│ ├── components/
│ │ ├── forms/ # Dynamic form components
-│ │ ├── pages/ # Page components
│ │ └── ui/ # UI components
│ ├── hooks/ # React hooks
│ ├── lib/ # Utilities
+│ ├── providers/ # Context providers (UserProvider, ProjectProvider)
+│ ├── queries/ # Data fetching, mutations and caching
+│ │ └── options/ # Query/mutation options organized by domain
│ ├── routes/ # File-based routing
│ │ ├── projects/
│ │ │ ├── $projectId/
@@ -98,9 +109,7 @@ client/
│ │ │ └── profile.tsx
│ │ └── __root.tsx
│ ├── app.tsx # App entry point
-│ ├── ipc.ts # IPC communication
-│ ├── sentry.ts # Error monitoring
-│ └── store.ts # State management
+│ └── index.ts # Error monitoring and router setup
├── build/ # Build resources (icons, etc.)
├── documentation/ # Developer docs
├── electron-builder.yml # Build configuration
@@ -111,7 +120,9 @@ client/
### Security
-As mentioned, elek.io Client only allows the Renderer Process to communicate with the Main Process via a controlled IPC API exposed through the Preload Script. This follows the [security best practices of Electron](https://www.electronjs.org/docs/latest/tutorial/security) and ensures that untrusted code running in the Renderer Process (e.g. third-party libraries, users content) cannot directly access Node.js APIs or the filesystem.
+Handling user content that is distributed and could potentially be malicious within an app that has access to the file system, strict security is necessary. elek.io Client follows [Electron's security best practices](https://www.electronjs.org/docs/latest/tutorial/security) to create strong isolation boundaries.
+
+The Renderer Process can only communicate with the Main Process via a controlled IPC API exposed through the Preload Script. This ensures that untrusted code running in the Renderer Process (e.g., third-party libraries or user content) cannot directly access Node.js APIs or the filesystem.
#### Renderer Process Isolation
@@ -131,14 +142,28 @@ A Content Security Policy is enforced via a `` tag in the `src/renderer/in
#### External Content Restrictions
-Some links to elek.io domains and loading of content are allowed in elek.io Client. To prevent abuse, the following restrictions are in place:
+Some links to elek.io domains and loading of content are allowed in elek.io Client. To prevent abuse and potential security risks, the following restrictions are in place:
**URL Whitelisting**:
-All external requests (e.g. when a user clicks a link inside the renderer or an Asset is displayed) are checked against a whitelist. See `allowedHostnamesToLoadExternal` in [`src/main/index.ts`](/src/main/index.ts).
+All external requests (e.g., when a user clicks a link inside the renderer or an Asset is displayed) are checked against a whitelist of allowed hostnames:
-Links to these hostnames are opened inside the default browser, not an elek.io Client renderer.
+- `elek-io-local-file://` (custom file protocol)
+- `localhost`
+- `elek.io`
+- `api.elek.io`
+- `github.com`
+
+See `allowedHostnamesToLoadExternal` in [`src/main/index.ts:41-46`](/src/main/index.ts) for the implementation.
+
+Links to whitelisted external hostnames are opened in the default system browser, not within an elek.io Client renderer window.
**Custom File Protocol**:
-Loading of Assets in the UI is handled via a custom file protocol `elek-io-local-file://` since the standard `file://` protocol in electron has more privileges than in a browser. This ensures path validation (must be within project or tmp folders) and prevents directory traversal.
+Loading of Assets in the UI is handled via a custom file protocol `elek-io-local-file://` since the standard `file://` protocol in Electron has more privileges than in a browser. This custom protocol implementation ensures path validation (files must be within Project or tmp folders) and prevents directory traversal attacks.
+
+See [`src/main/index.ts:267-305`](/src/main/index.ts) for the custom protocol implementation.
+
+---
+
+**Last Updated:** 2025-12-01
diff --git a/documentation/renderer/breadcrumb-navigation.md b/documentation/renderer/breadcrumb-navigation.md
new file mode 100644
index 00000000..c2928571
--- /dev/null
+++ b/documentation/renderer/breadcrumb-navigation.md
@@ -0,0 +1,146 @@
+# Breadcrumb Navigation
+
+## Overview
+
+The breadcrumb system provides automatic breadcrumb navigation based on TanStack Router's route hierarchy. It displays the navigation path in the application header, allowing users to understand their current location and navigate back through the hierarchy.
+
+**Key Features:**
+
+- **Route-based:** Automatically tracks route matches from TanStack Router
+- **Dynamic labels:** Supports both static strings and dynamic content (e.g., project names, entry titles)
+
+## Architecture
+
+The breadcrumb system consists of three main parts:
+
+### BreadcrumbProvider (`src/renderer/providers/BreadcrumbProvider.tsx`)
+
+The provider manages breadcrumb state using a Map and TanStack Router's `useMatches()` hook.
+It's `setBreadcrumb()` method adds a new breadcrumb to the map, using path as the key.
+
+When computing the current breadcrumbs, it iterates over the active route matches provided by `useMatches()`. Each match contains (along with other properties) the `routeId` and `pathname`.
+E.g. if you are currently at `/projects/32ba1a3c-c775-4ff0-b5cd-08c71edfe18b/dashboard`, the matches would be:
+
+```typescript
+[
+ { routeId: '__root__', pathname: '/' },
+ { routeId: '/projects', pathname: '/projects' },
+ {
+ routeId: '/projects/$projectId',
+ pathname: '/projects/32ba1a3c-c775-4ff0-b5cd-08c71edfe18b',
+ },
+ {
+ routeId: '/projects/$projectId/dashboard',
+ pathname: '/projects/32ba1a3c-c775-4ff0-b5cd-08c71edfe18b/dashboard',
+ },
+];
+```
+
+For each match, it looks up the corresponding breadcrumb in its internal map by `pathname` to build the breadcrumb trail, returning an ordered array of breadcrumbs.
+
+**Provider Location:** Wraps the entire application in `src/renderer/routes/__root.tsx:18`
+
+```tsx
+
+ {/* App content */}
+
+```
+
+### useBreadcrumb Hook (`src/renderer/hooks/useBreadcrumb.ts`)
+
+A hook that registers breadcrumbs for specific routes.
+
+```typescript
+export interface BreadcrumbContextValue {
+ breadcrumbs: Breadcrumb[];
+ setBreadcrumb: (path: string, label: string) => void;
+ clearBreadcrumb: (path: string) => void;
+}
+
+function useBreadcrumb(
+ route?: AnyRoute,
+ label?: string | undefined
+): BreadcrumbContextValue;
+```
+
+The hook takes an optional `route` (returned value of `createFileRoute()`) and `label`. If provided, it uses the providers `setBreadcrumb()` method to register the current path of `route.pathname` with the given `label`.
+
+## Usage Patterns
+
+### Static Breadcrumb Label
+
+For routes with fixed labels (e.g., "Settings", "Dashboard"):
+
+```tsx
+// src/renderer/routes/projects/$projectId/settings.tsx
+import { useBreadcrumb } from '@renderer/hooks/useBreadcrumb';
+
+export const Route = createFileRoute('/projects/$projectId/settings')({
+ component: ProjectSettingsLayout,
+});
+
+function ProjectSettingsLayout(): ReactElement {
+ useBreadcrumb(Route, 'Settings');
+
+ return ;
+}
+```
+
+**Result:** Displays "Settings" in the breadcrumb trail
+
+### Dynamic Breadcrumb Label
+
+For routes with data-dependent labels (e.g., project name, entry title):
+
+```tsx
+// src/renderer/routes/projects/$projectId.tsx
+import { useBreadcrumb } from '@renderer/hooks/useBreadcrumb';
+import { useProject } from '@renderer/hooks/useProject';
+
+function ProjectLayoutContent(): React.JSX.Element {
+ const {
+ projectQuery: { data: project, isPending: isReadingProject },
+ } = useProject();
+
+ // Pass undefined while loading
+ useBreadcrumb(Route, isReadingProject === false ? project.name : undefined);
+
+ return ;
+}
+```
+
+**Result:** Displays the project name in the breadcrumb trail
+
+### Accessing Breadcrumb Context
+
+To access current breadcrumbs without registering a new one:
+
+```tsx
+// src/renderer/components/user-header.tsx
+import { useBreadcrumb } from '@renderer/hooks/useBreadcrumb';
+
+export function UserHeader(): React.JSX.Element {
+ const { breadcrumbs } = useBreadcrumb();
+
+ return (
+
+
+ {breadcrumbs.map((crumb, index, array) => (
+
+
+
+ {crumb.label}
+
+
+ {array.length !== index + 1 && }
+
+ ))}
+
+
+ );
+}
+```
+
+---
+
+**Last Updated:** 2025-12-04
diff --git a/documentation/renderer/dynamic-form-field-generation.md b/documentation/renderer/dynamic-form-field-generation.md
index a16627e2..14972a46 100644
--- a/documentation/renderer/dynamic-form-field-generation.md
+++ b/documentation/renderer/dynamic-form-field-generation.md
@@ -1,62 +1,209 @@
-# Dynamic form field generation
+# Dynamic Form Field Generation
-Handling static forms that stay the same is straight forward. We simply use the correct input component and attach it to the forms field. But when it comes to user defined forms it gets tricky.
+## Overview
-We want to be able to define a form field in a way that it can be rendered dynamically. This means we need to define what type of input it is, what label and description it has, if it is required and so on.
+Static forms with predefined fields are straightforward to implement. However, elek.io allows users to define their own content structures through Collections, which means forms must be generated dynamically based on user-defined schemas.
-This is done using field definitions. A field definition is simply a JSON object that contains all the necessary information to render a form field.
+**Why Dynamic Forms?**
-When a user creates a Collection he can define those field definitions:
+Users need the flexibility to create custom content types without modifying code. For example:
+
+- A blog might need: title (text), content (textarea), published (boolean)
+- A product catalog might need: name (text), price (number), inStock (boolean), images (file)
+- An event calendar might need: title (text), date (date), location (text)
+
+We can't hardcode these forms since they are user-defined, instead we use **field definitions** to describe each field's properties, allowing the UI to render appropriate form controls dynamically.
+
+## Field Definitions
+
+A field definition is a JSON object that contains all necessary information to render and validate a form field.
+
+**Example field definition for a blog post title:**
```typescript
{
id: '467e57ea-e04a-44a7-b34b-684ed3ba6f49',
- valueType: 'string',
- fieldType: 'text',
- label: {
+ valueType: 'string', // Data type for validation
+ fieldType: 'text', // UI component to render
+ label: { // Label with translations
en: 'Title',
de: 'Titel',
},
- description: {
+ description: { // Description with translations
en: 'A short title for the Entry',
de: 'Ein kurzer Titel für den Eintrag',
},
- min: null,
- max: 100,
- defaultValue: null,
- inputWidth: '12',
- isDisabled: false,
- isRequired: true,
- isUnique: false,
+ min: null, // Minimum length (for strings/arrays)
+ max: 100, // Maximum length
+ defaultValue: null, // Default value when creating new Entry
+ inputWidth: '12', // Grid column width (1-12)
+ isDisabled: false, // Whether field is read-only
+ isRequired: true, // Whether field must be filled
+ isUnique: false, // Whether value must be unique across Entries
}
```
-## UI for defining field definitions
+**Key Properties:**
+
+Depending on the field type, definitions may include different properties, but common ones are:
+
+- `id`: Unique identifier for this field
+- `valueType`: The data type (`string`, `number`, `boolean`, etc.) - used for validation
+- `fieldType`: The UI component type (`text`, `textarea`, `number`, `switch`, `select`, etc.)
+- `label` / `description`: Translatable strings for multiple languages that help users understand the field's purpose
+- `min` / `max`: Validation constraints
+- `isRequired`: Whether the field must have a value
+- `inputWidth`: Responsive grid width (using 12-column grid)
+
+## Creating Field Definitions via a Collection
+
+When users create or update a Collection, they use a visual editor to define the fields for that Collection type.
+
+**Location:** Collection forms are in [`components/forms/collection-form.tsx`](../../src/renderer/components/forms/collection-form.tsx) and used in routes like [`routes/projects/$projectId/collections/create.tsx`](../../src/renderer/routes/projects/$projectId/collections/create.tsx) and [`routes/projects/$projectId/collections/$collectionId/update.tsx`](../../src/renderer/routes/projects/$projectId/collections/$collectionId/update.tsx)
+
+**Features:**
+
+- Add, remove, and reorder fields via drag-and-drop
+- Select field type (text, textarea, number, boolean, select, etc.)
+- Set labels and descriptions with multi-language support
+- Configure validation rules (min/max length, required, unique)
+- Set default values and input width
+- Preview how the form will look to content editors
+
+## Rendering Dynamic Forms
+
+When rendering a form to create or edit an Entry, we iterate over the Collection's field definitions and render appropriate form controls for each field.
+
+**Location:** Entry forms are in [`components/forms/entry-form.tsx`](../../src/renderer/components/forms/entry-form.tsx) and used in routes like [`routes/projects/$projectId/collections/$collectionId/create.tsx`](../../src/renderer/routes/projects/$projectId/collections/$collectionId/create.tsx) and [`routes/projects/$projectId/collections/$collectionId/$entryId/update.tsx`](../../src/renderer/routes/projects/$projectId/collections/$collectionId/$entryId/update.tsx)
+
+### Component Architecture
+
+The dynamic form rendering system is built with layered components, each adding functionality:
+
+#### Base UI Components
+
+Located in [`components/ui/`](../../src/renderer/components/ui/):
+
+- **`Input`**, **`Textarea`**, **`Switch`**, **`Slider`**, **`Select`**: Basic HTML form controls with styling and [Radix UI primitives](https://www.radix-ui.com/primitives) for accessibility
+- These are the fundamental building blocks used across the application
+
+#### Form Field Components
+
+Located in [`components/ui/form.tsx`](../../src/renderer/components/ui/form.tsx):
+
+**1. `FormComponentFromFieldDefinition`** (line ~290)
+
+- Takes a field definition and renders the appropriate input component
+- Handles value transformation (e.g., converting string inputs to numbers)
+- Returns `null` for empty values instead of empty strings (important for validation)
+- Maps `fieldType` to the correct UI component
+
+**2. `FormComponentFromFieldDefinitionTranslatable`** (line ~420)
+
+- Extends `FormComponentFromFieldDefinition`
+- Adds multi-language support for translatable fields
+- Renders a dialog for entering translations in all supported languages
+- Shows language switcher button next to field
+
+**3. `FormFieldFromDefinition`** (recommended entry point)
+
+- Use this component when rendering form fields from definitions
+- Wraps `FormComponentFromFieldDefinitionTranslatable` with:
+ - Label (with required indicator)
+ - Description text
+ - Validation error messages
+ - Proper spacing and layout
+
+### Example Usage
+
+```typescript
+import { FormFieldFromDefinition } from '@renderer/components/ui/form';
+
+// In your component:
+const collection = /* ... Collection with fieldDefinitions ... */;
+
+return (
+
+
+);
+```
+
+## Validation with Zod
-When creating or updating a Collection, the user can add, remove and edit field definitions. You can find the UI for that under [`src/renderer/components/pages/create-update-collection-page.tsx`](/src/renderer/components/pages/create-update-collection-page.tsx). It allows the user to select the field type (e.g. text, number, boolean etc.), set the label and description (with support for multiple languages), define validation rules (e.g. min/max length, if it's required etc.).
+All user input validation uses [Zod](https://zod.dev/), a TypeScript-first schema validation library.
-## Rendering the dynamic form
+### Schema Generation
-When the form to **create a new Entry** for the Collection is rendered, we loop over the field definitions and render the corresponding input component that adheres to the defined type and limitations (e.g. min/max length, if it's required etc.).
+**Source:** [@elek-io/core](https://github.com/elek-io/core)
-This way we can render forms dynamically based on user defined field definitions.
+Core provides utilities to convert field definitions into Zod schemas:
-### UI Component overview
+- **`getValueSchemaFromFieldDefinition()`**: Generates a Zod schema for a single field
+- See [schema generation utilities](https://github.com/elek-io/core/blob/main/src/schema/schemaFromFieldDefinition.ts) for more functions
-The following components are used:
+**Example:**
-- The [`Input`](/src/renderer/components/ui/input.tsx), [`Textarea`](/src/renderer/components/ui/textarea.tsx), [`Switch`](/src/renderer/components/ui/switch.tsx), [`Slider`](/src/renderer/components/ui/slider.tsx) and [`Select`](/src/renderer/components/ui/select.tsx) are basic HTML inputs with added styling and [Radix UI primitives](https://www.radix-ui.com/primitives) for accessibility.
-- The components [`FormInputField`, `FormTextareaField`, `FormRangeField` and others](/src/renderer/components/ui/form.tsx) wrap the corresponding basic HTML component and transform the value the user put in (e.g. in case of an Input of type "number" to a number) before handing it back to the attached forms field. This is done because the inputs value internally is always a string. But this does become a problem when the form requires the type to be a number. It also returns null instead of an empty string if the user does not put in a value, since a form can allow for strings with a minimum lenght and null but an empty string should fail the validation.
-- The [`FormComponentFromFieldDefinition`](/src/renderer/components/ui/form.tsx) simply takes a fieldDefinition and renders a component based on it.
-- The [`FormComponentFromFieldDefinitionTranslatable`](/src/renderer/components/ui/form.tsx) extends the FormComponentFromFieldDefinition. If there are multiple supported languages, it renders a button next to the field that opens a dialog where translations for all supported languages can be entered.
-- Finally, the [`FormFieldFromDefinition`](/src/renderer/components/ui/form.tsx) wraps the FormComponentFromFieldDefinitionTranslatable in a FormItem and adds a label, description and validation message. This is the component that should be used when rendering a form field based on a field definition.
+```typescript
+import { getValueSchemaFromFieldDefinition } from '@elek-io/core';
-## Generating validation schemas and types
+const fieldDefinition = {
+ valueType: 'string',
+ min: 5,
+ max: 100,
+ isRequired: true,
+ // ...
+};
+
+// Generated schema validates:
+// - Type is string
+// - Length between 5-100 characters
+// - Field is required (not null/undefined)
+const schema = getValueSchemaFromFieldDefinition(fieldDefinition);
+```
+
+### Form Validation
+
+Use the generated Zod schema with [React Hook Form's Zod resolver](https://react-hook-form.com/get-started#SchemaValidation):
+
+```typescript
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { getUpdateEntrySchemaFromFieldDefinitions } from '@elek-io/core';
+
+const collection = /* ... */;
+
+// Generate schema from all field definitions
+const schema = getUpdateEntrySchemaFromFieldDefinitions(
+ collection.fieldDefinitions
+);
+
+// Create form with validation
+const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: { /* ... */ }
+});
+
+// Form validates automatically and shows error messages via FormMessage component
+```
-All validation of user input is done using [Zod](https://zod.dev/). elek.io Core provides [predefined Zod schemas for all supported field types](https://github.com/elek-io/core/blob/main/src/schema/fieldSchema.ts).
+**Benefits:**
-Via the `getValueSchemaFromFieldDefinition()` or any other [schema generating function inside `@elek-io/core`](https://github.com/elek-io/core/blob/main/src/schema/schemaFromFieldDefinition.ts) the field definitions are converted to Zod schemas for validation and infered TypeScript types.
+- Type-safe form data (inferred from generated Zod schema)
+- Automatic validation based on field definitions
+- Consistent validation between frontend and backend
+- User-friendly error messages
+- Prevents invalid data from reaching Core
-## Validating generated forms
+---
-This schema can then be used in conjunction with the [React Hook Form Zod resolver](https://react-hook-form.com/get-started#SchemaValidation) to validate the user input before the form is submitted to `@elek-io/core` via IPC and provide feedback to the user if the input is invalid.
+**Last Updated:** 2025-12-01
diff --git a/documentation/renderer/loading-and-updating-data.md b/documentation/renderer/loading-and-updating-data.md
new file mode 100644
index 00000000..e559e859
--- /dev/null
+++ b/documentation/renderer/loading-and-updating-data.md
@@ -0,0 +1,390 @@
+# Loading and Updating Data
+
+## Overview
+
+elek.io Client uses [TanStack Query](https://tanstack.com/query/latest) (formerly React Query) for all data fetching and mutations. This provides a consistent, type-safe way to interact with the main process via IPC, with built-in caching, loading states, and error handling.
+
+## Configuration
+
+### Query Client Setup
+
+The base configuration for all queries and mutations is in [`client.ts`](../../src/renderer/queries/client.ts):
+
+**Key Settings:**
+
+- `staleTime: Infinity` (line 9) - Queries remain "fresh" indefinitely and won't automatically refetch. This is appropriate for local data that only changes through user actions.
+
+### Query and Mutation Options
+
+All `queryOptions` and `mutationOptions` are centrally defined in the [`options/`](../../src/renderer/queries/options/) directory with separate files for each domain (`projectOptions.ts`, `collectionOptions.ts`, `entryOptions.ts`, `assetOptions.ts`, `userOptions.ts`, `apiOptions.ts`) and re-exported through [`queries/index.ts`](../../src/renderer/queries/index.ts). These files contain:
+
+- Typed wrappers around IPC calls
+- Query keys for cache management
+- Automatic cache updates on mutations - Mutations automatically update related queries' cache data. For example, when you create a Project, both the individual Project cache AND the Projects list cache are updated without requiring a refetch.
+- Metadata for toast notifications (method and objectType)
+
+## Querying Data
+
+### Using Context Providers for User and Project Data
+
+For accessing the current user and project data, prefer using the `useUser()` and `useProject()` hooks instead of directly calling `useQuery()` with `queryOptions.user.get()` or `queryOptions.projects.read()`.
+
+**Benefits:**
+
+- Reduces the number of active query observers - the providers create a single query that's shared across all components
+- Provides convenient utility methods (`formatDatetime`, `translateContent`) that automatically use the current user/project context
+- Ensures consistent data access patterns throughout the application
+- Better performance with fewer unnecessary query subscriptions
+
+#### UserProvider and useUser Hook
+
+The [`UserProvider`](../../src/renderer/providers/UserProvider.tsx) wraps the entire application at the root level (see [`routes/__root.tsx:130`](../../src/renderer/routes/__root.tsx)). It provides:
+
+- `userQuery: UseQueryResult` - The current user query result
+- `formatDatetime: (props: FormatDatetimeProps) => { relative: string; absolute: string }` - Formats datetime strings according to the user's locale
+
+**Example:** See [`components/user-header.tsx`](../../src/renderer/components/user-header.tsx)
+
+```typescript
+import { useUser } from '@renderer/hooks/useUser';
+
+function UserHeader() {
+ const { userQuery, formatDatetime } = useUser();
+
+ // Access user data
+ const user = userQuery.data;
+
+ // Format a datetime
+ const formattedDate = formatDatetime({ datetime: user?.createdAt });
+
+ return (
+
+
{user?.name}
+
Joined {formattedDate.relative}
+
+ );
+}
+```
+
+#### ProjectProvider and useProject Hook
+
+The [`ProjectProvider`](../../src/renderer/providers/ProjectProvider.tsx) wraps all project-specific routes (see [`routes/projects/$projectId.tsx:14`](../../src/renderer/routes/projects/$projectId.tsx)). It provides:
+
+- `projectId: string` - The current project ID from the route
+- `projectQuery: UseQueryResult` - The current project query result
+- `userQuery: UseQueryResult` - The current user query result (inherited from UserProvider)
+- `formatDatetime: (props: FormatDatetimeProps) => { relative: string; absolute: string }` - Datetime formatter (inherited from UserProvider)
+- `translateContent: (props: TranslateContentProps) => string` - Translates user-defined content to the current user's language, falling back to the project's default language, then English
+
+**Example:** See [`components/project-sidebar.tsx`](../../src/renderer/components/project-sidebar.tsx)
+
+```typescript
+import { useProject } from '@renderer/hooks/useProject';
+
+function ProjectSettings() {
+ const { projectQuery, translateContent } = useProject();
+
+ // Access project data
+ const project = projectQuery.data;
+
+ // Translate content
+ const title = translateContent({
+ key: 'project.title',
+ record: project?.name // TranslatableString object
+ });
+
+ return
{title}
;
+}
+```
+
+### Non-Blocking Page Loads
+
+We prioritize fast, responsive UI by rendering pages immediately without waiting for data to load. Navigation between pages should feel instantaneous.
+
+**Pattern:** Show Skeleton components while data loads, then replace them with actual content when ready.
+
+**Example:** See [`routes/projects/index.tsx`](../../src/renderer/routes/projects/index.tsx)
+
+```typescript
+const { data: projects, isPending } = useQuery(
+ queryOptions.projects.list({ limit: 0 })
+);
+
+return (
+
+);
+```
+
+This ensures the page structure renders immediately, with placeholders that are replaced by real data as it loads.
+
+### Error Handling - Prefer useQueryNoError
+
+**Always use [`useQueryNoError`](../../src/renderer/hooks/useQueryNoError.ts) instead of `useQuery` for data fetching.**
+
+This hook solves a TypeScript limitation where `data` is typed as `T | undefined` even with `throwOnError: true`. It manually throws errors to the error boundary and returns a narrowed type without error states.
+
+**Example:** See [`routes/projects/$projectId/collections/$collectionId/index.tsx`](../../src/renderer/routes/projects/$projectId/collections/$collectionId/index.tsx)
+
+```typescript
+import { useQueryNoError } from '@renderer/hooks/useQueryNoError';
+
+const { data: collection, isPending } = useQueryNoError(
+ queryOptions.collections.read({ projectId, id: collectionId })
+);
+
+if (isPending) return ;
+
+// data is now safely typed as defined (no error or loading states)
+return
{collection.name}
;
+```
+
+See the hook's JSDoc for implementation details. Errors are caught by the root `ErrorComponent` in [`routes/__root.tsx`](../../src/renderer/routes/__root.tsx).
+
+**Naming Conventions:**
+
+When destructuring `useQueryNoError`, follow these patterns based on the query operation:
+
+- `data` → Name it as the resulting object (e.g., `project`, `collection`, `entries`)
+- `isPending` → Prefix with the query operation (e.g., `isReadingProject`, `isListingProjects`, `isReadingCollection`)
+
+Always look at the `queryOptions` method used to determine the correct names:
+
+```typescript
+// queryOptions.projects.read() → reading a single project
+const { data: project, isPending: isReadingProject } = useQueryNoError(
+ queryOptions.projects.read({ id: projectId })
+);
+
+// queryOptions.projects.list() → listing multiple projects
+const { data: projects, isPending: isListingProjects } = useQueryNoError(
+ queryOptions.projects.list({ limit: 0 })
+);
+
+// queryOptions.collections.read() → reading a single collection
+const { data: collection, isPending: isReadingCollection } = useQueryNoError(
+ queryOptions.collections.read({ projectId, id: collectionId })
+);
+```
+
+## Mutating Data
+
+### Form Mutations with Existing Data
+
+When building forms to update existing data, we need to handle the loading state gracefully. Forms need to be populated with current values, but those values come from a query that may not be resolved yet.
+
+**Pattern:** Use `