Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
45772ab
refactor: rename transcribe page to workspace and remove console logs
viperehonchuk May 29, 2026
d70ee72
docs: update conventions and add workspace image viewer specifications
viperehonchuk May 29, 2026
e68b477
refactor: switch to minimal vitest reporter and add error logging
viperehonchuk May 29, 2026
823b9b6
feat: implement page name editing and transcription table locking
viperehonchuk May 29, 2026
1d13d2d
refactor: modularize project page and update opencode command settings
viperehonchuk May 29, 2026
f8fca10
feat: add specifications for workspace enhancements and enable lsp
viperehonchuk May 29, 2026
f29e489
Merge branch 'feat/transcribe-projects' into feat/transcribe-workspace
viperehonchuk May 31, 2026
a2a8f9c
feat: add transcription table sizing spec and improve workspace compo…
viperehonchuk May 31, 2026
80757c7
feat(transcribe): implement wrapping and auto-expanding textareas in …
viperehonchuk May 31, 2026
579f10c
feat(server): add transcription support to project image PATCH route
viperehonchuk May 31, 2026
d8af2b2
feat: implement interval-based auto-save and window close preservatio…
viperehonchuk May 31, 2026
b4241cd
Merge branch 'feat/transcribe-projects' into feat/transcribe-workspace
viperehonchuk Jun 4, 2026
08abab0
test(workspace): stabilize tests with deterministic UUIDs and waitFor
viperehonchuk Jun 4, 2026
934613b
Merge branch 'feat/transcribe-projects' into feat/transcribe-workspace
viperehonchuk Jun 5, 2026
5d69c4d
feat(transcribe): implement project management components and workspa…
viperehonchuk Jun 5, 2026
e5ff3eb
feat(transcription-table): focus first cell after adding rows
viperehonchuk Jun 5, 2026
08ad410
Merge branch 'feat/transcribe-projects' into feat/transcribe-workspace
viperehonchuk Jun 5, 2026
4d31500
fix(database): use Kysely sql for unixepoch updated_at
viperehonchuk Jun 5, 2026
e220e17
feat(transcribe): hydrate transcription rows from saved image transcr…
viperehonchuk Jun 5, 2026
0fb7d6c
feat(transcription-table): confirm row deletion in TranscriptionTable
viperehonchuk Jun 5, 2026
1a45951
feat(transcribe): add CSV export for transcription rows
viperehonchuk Jun 5, 2026
4c3364f
feat(spread-split): support spread splitting and revert
viperehonchuk Jun 10, 2026
2634681
feat(transcribe): support partial ISO date parsing and date column type
viperehonchuk Jun 10, 2026
4c4f94f
test(project-header): align fixture with confession-list type
viperehonchuk Jun 10, 2026
574afe4
Merge branch 'feat/transcribe-projects' into feat/transcribe-workspace
viperehonchuk Jun 10, 2026
21fbc88
fix(locationiq): correct locationiq search location filtering
viperehonchuk Jun 10, 2026
4167aae
fix(transcribe): render SpatialInput dropdown via KnownLocations prov…
viperehonchuk Jun 10, 2026
f72a4bd
test(createImageSource): assert transactional insert builder call chain
viperehonchuk Jun 10, 2026
1a99bf0
feat(transcribe): display projects as card grid with dates
viperehonchuk Jun 10, 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
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ NEXT_PUBLIC_GISCUS_CATEGORY_ID=
GEMINI_API_KEY=
NEXT_PUBLIC_OAUTH_CLIENT_ID=
OAUTH_CLIENT_SECRET=
NEXT_PUBLIC_API_SITE=http://localhost:4000
NEXT_PUBLIC_API_SITE=http://localhost:4000

# Optional: LocationIQ API key for location autocomplete in SpatialInput.
# Obtain a free key at https://locationiq.com
NEXT_PUBLIC_LOCATIONIQ_KEY=
2 changes: 1 addition & 1 deletion .opencode/commands/refactor.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
description: 'Refactor a specific file for performance, architectural alignment, and readability.'
model: 'opencode/claude-opus-4-6
temperature: 0.1
top_p: 0.90
top_p: 0.95
max_tokens: 8192
---

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ You operate in two mutually exclusive zones. Before writing any code, determine
### ZONE A: Frontend (Next.js 15, React 19)

- Trigger: File path does NOT begin with `src/server/`.
- Ruleset: Read `@CONVENTIONS.md`.
- Ruleset: Read `CONVENTIONS.md`.
- Constraint: Never apply Node.js ESM strict `.js` imports. Default to React Server Components.

### ZONE B: Backend (Node.js 22, Hono)
Expand Down
7 changes: 7 additions & 0 deletions CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- **Component Exports**: Use `export default function ComponentName` for all components and pages. Avoid named exports for React components unless grouping multiple sub-components in a single file.
- **API requests**: Never done from React components, but only from separate async service functions. Use `fetch`.
- **File Length**: Refactor & split files beyond 300 lines.
- **SRP**: Strictly align to the Single Responsibility Principle. If a module does more than one conceptual thing, split it.

## 4. Styling & UI

Expand All @@ -42,3 +43,9 @@

- **Coverage**: Generate `.test.ts` or `.test.tsx` files for every new component or service utility.
- **Syntax**: Use Vitest (`describe`, `it`, `expect`).

## 7. Nested apps (Hono server)

- **Scope**: `src/server` folder contains server code.
- **Conventions**: Server code has its own conventions in `src/server/CONVENTIONS.md`
- **Linting**: Server code is linted by the same ESLint configuration as the frontend.
5 changes: 5 additions & 0 deletions TESTING_CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@
- **Test Runner API:** Use `describe`, `it`, and `expect` exclusively from `vitest`.
- **Spying:** Exclusively use `vi.fn()` and `vi.spyOn()`. Jest imports are strictly forbidden.
- **Zero-Tolerance for `any`:** The `any` type is strictly banned. Use `unknown` for ambiguous data payloads, or leverage `DeepPartial<T>` and `ReturnType<typeof fn>` to type incomplete mocked genealogical records or query responses accurately.

## 7. Nested apps (Hono server)

- **Scope**: `src/server` folder contains server code.
- **Configuration**: Server code has its own Vitest configuration used by `cd src/server` to it.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"react": "~19.0.0",
"react-dom": "~19.0.0",
"react-hook-form": "^7.71.2",
"react-imask": "^7.6.1",
"react-leaflet": "~5.0.0",
"react-leaflet-cluster": "^4.0.0",
"react-paginate": "~8.3.0",
Expand Down Expand Up @@ -61,6 +62,7 @@
"husky": "^9.1.7",
"jsdom": "^26.0.0",
"lint-staged": "^15.4.3",
"msw": "^2.14.6",
"prettier": "^3.5.3",
"schema-dts": "^1.1.5",
"stylelint": "^16.2.1",
Expand All @@ -86,7 +88,7 @@
"artifacts:clean": "./scripts/clean-repo/clean-repo.sh",
"build": "next build",
"data:modernize-russian": "tsx src/modernize-russian/index.ts",
"dev": "next dev --turbopack",
"dev": "PORT=3001 next dev --turbopack",
"dev:server": "cd src/server && yarn dev",
"docker:typesense:remove": "docker rm typesense-server",
"docker:typesense:start": "tsx src/dockerize-typesense/index.ts",
Expand Down
17 changes: 4 additions & 13 deletions specs/000-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,37 +49,28 @@ context:

---

## 4. Hard Constraints

- **React 19:** Do not implement manual `useMemo`/`useCallback` unless explicitly bypassing the React Compiler. Maintain strict Client/Server boundaries.
- **Backend ESM:** All relative imports in Hono/Node.js files MUST terminate with explicit `.js` extensions.
- **Isolation:** Do not modify schemas, context files, or unrelated components not explicitly listed in the `targets` frontmatter.

---

## 5. Agentic Verification
## 4. Agentic Verification

Execute the following commands to validate the implementation:

1. **Type & Lint Pass:** Run standard formatting and type checks.

```bash
/lint
./scripts/opencode-check.sh src/path/to/file.ts # Both frontend and backend

```

2. **Targeted Test Execution:** Run the specific route or backend test.

```bash
/test-route src/path/to/test.test.tsx
yarn test src/path/to/test.test.tsx
# OR
/test-server
cd src/server && yarn test src/path/to/test.test.ts

```

3. **ESM Validation (Backend Only):**

```bash
/verify-esm [Target File Path]

```
70 changes: 70 additions & 0 deletions specs/056-workspace-back-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
description: Add a navigation link to the transcription workspace allowing users to return to the project metadata page
status: draft
targets:
- src/app/transcribe/workspace/page.tsx
context:
- src/app/transcribe/workspace/page.module.css
---

# Workspace to Project Navigation

## 1. Architectural Boundary

- **Execution Context:** Client (Next.js & React 19)
- **Data Scope:** N/A

---

## 2. State Transition Matrix

### Fault / Current State

- **Condition:** User enters the transcription workspace view at `/transcribe/workspace/?projectId=[id]`.
- **Behavior:** The workspace lacks a dedicated UI element to navigate back to the project configuration page (`/transcribe/project/?projectId=[id]`) to edit metadata or modify the image set.
- **Log/Trace:**

```tsx
// Current table header lacks back navigation
<div className={styles.tableHeader}>
<h2>Транскрипція</h2>
<button className={styles.addButton} onClick={addRow}>
<Plus size={16} />
Додати рядок
</button>
</div>
```

### Target / Resolved State

- **Condition:** User enters the transcription workspace view.
- **Behavior:** The user is presented with a clear "Back to Project" button/link (e.g., using `lucide-react` `ArrowLeft` icon). Clicking this element redirects to `/transcribe/project/?projectId=[projectId]`.
- **Schema/Type Alteration:**

```tsx
// No schema change; UI alteration only
```

---

## 3. Execution Pipeline

### 3.1. src/app/transcribe/workspace/page.tsx

1. Import `Link` from `next/link` or an appropriate icon like `ArrowLeft` from `lucide-react`.
2. Locate a suitable header region (e.g., above or inside `styles.tableHeader`, or at the top of the workspace layout).
3. Inject the navigation element to route to `/transcribe/project/?projectId=${projectId}`.
4. Ensure the element is only rendered when `projectId` is available in state (though the component handles missing `projectId` by returning early with a loading state).
5. If necessary, apply class names and update `src/app/transcribe/workspace/page.module.css` for consistent spacing and alignment.

---

## 4. Agentic Verification

Execute the following commands to validate the implementation:

1. **Type & Lint Pass:** Run standard formatting and type checks.

```bash
/lint src/app/transcribe/workspace/page.tsx
```
74 changes: 74 additions & 0 deletions specs/057-workspace-image-viewer-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
description: Display current image in workspace and persist selection in browser storage
status: draft
targets:
- src/app/transcribe/workspace/page.tsx
context: []
---

# Workspace Persistent Image Viewer

## 1. Architectural Boundary

- **Execution Context:** Client (Next.js & React 19)
- **Data Scope:** Local State (Browser LocalStorage)

---

## 2. State Transition Matrix

### Fault / Current State

- **Condition:** User accesses the transcription workspace page.
- **Behavior:** The left panel shows a static placeholder regardless of the loaded project images. There is no state tracking which image is currently selected, nor any persistence across browser sessions.
- **Log/Trace:**

```tsx
<div className={styles.imagePlaceholder}>
<ImageIcon size={64} className={styles.placeholderIcon} />
<p className={styles.placeholderText}>Зображення для транскрибування</p>
<p className={styles.placeholderSubtext}>(Панель перегляду)</p>
</div>
```

### Target / Resolved State

- **Condition:** User views the transcription workspace and multiple project images are loaded.
- **Behavior:** The left panel renders the currently selected image from the `images` array. The index of the current image is tracked in state and mirrored to `localStorage` (scoped by `projectId`). When the session is restarted, the component restores the last viewed image index from storage.
- **Schema/Type Alteration:**

```ts
// Local state for the selected index
const [currentImageIndex, setCurrentImageIndex] = useState<number>(0);
```

---

## 3. Execution Pipeline

### 3.1. src/app/transcribe/workspace/page.tsx

1. Introduce `currentImageIndex` state to keep track of the currently displayed image from the `images` array.
2. Implement a `useEffect` hook that runs on mount (when `projectId` is available and images are loaded) to read the stored index from `localStorage` using a project-specific key (e.g., `koreni_workspace_${projectId}_image_index`). Ensure it defaults to `0` and handles out-of-bounds cases.
3. Implement a `useEffect` hook that writes the `currentImageIndex` to `localStorage` whenever it changes, ensuring the value is persisted for the next session.
4. Replace the static `.imagePlaceholder` in the left panel with an Next.js `<Image>` using the `url` from `images[currentImageIndex]`.
5. Update the `.imageInfo` span in `.viewerHeader` to display the metadata (e.g., `storageKey`) of the currently selected image, rather than always showing the first one (`images[0]`).
6. Add Previous/Next navigation controls in the UI to allow changing the `currentImageIndex`, with boundary checks (min 0, max `images.length - 1`).

---

## 4. Agentic Verification

Execute the following commands to validate the implementation:

1. **Type & Lint Pass:** Run standard formatting and type checks.

```bash
/lint src/app/transcribe/workspace/page.tsx
```

2. **Targeted Test Execution:** Run the specific route test or overall app test to ensure no regression.

```bash
/test src/app/transcribe/workspace/page.tsx
```
90 changes: 90 additions & 0 deletions specs/058-workspace-image-zoom-scroll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
description: Implement horizontal and vertical zoom and pan capabilities for the workspace Image component.
status: draft
targets:
- src/app/transcribe/workspace/page.tsx
---

# Workspace Image Zoom and Scroll

## 1. Architectural Boundary

- **Execution Context:** Client (Next.js & React 19)
- **Data Scope:** Local State

---

## 2. State Transition Matrix

### Fault / Current State

- **Condition:** The `<Image />` component within the transcribe workspace lacks native zoom and two-dimensional pan capabilities.
- **Behavior:** Users cannot visually inspect specific regions of the document by zooming in and scrolling across the image with their mouse.
- **Log/Trace:**

```ts
// Currently lacks responsive scale and translation state mechanics
const WorkspaceImage = () => {
return <Image src="..." />;
}
```

### Target / Resolved State

- **Condition:** The workspace image view requires robust navigational controls for detailed image analysis.
- **Behavior:** The `<Image />` component dynamically reflects local state for scale, translateX, and translateY. It registers native mouse events for wheel zoom and pointer drag panning in both directions.
- **Schema/Type Alteration:**

```ts
interface TransformState {
scale: number;
x: number;
y: number;
}

interface DragState {
isDragging: boolean;
startX: number;
startY: number;
}
```

---

## 3. Execution Pipeline

### 3.1. src/app/transcribe/workspace/page.tsx

1. **Transform State Management:** Introduce local state variables for `scale`, `translateX`, and `translateY`. Ensure reasonable constraints (e.g., minimum scale > 0.1, maximum scale).
2. **Wheel Zooming:**
- Add a wheel event handler on the image container.
- Calculate the delta from `event.deltaY` to increment or decrement the `scale` state.
- Prevent default scroll behavior while hovering the image block.
3. **Button Zooming:**
- Integrate UI buttons for explicit Zoom In and Zoom Out operations that update the `scale` state.
4. **Drag Scrolling (Panning):**
- Bind `onMouseDown` to the image container to initialize dragging state (`isDragging = true`) and record the initial `clientX` / `clientY`.
- Bind `onMouseMove` to calculate the distance moved since the last point, and update `translateX` and `translateY` states accordingly.
- Bind `onMouseUp` and `onMouseLeave` to terminate the dragging sequence (`isDragging = false`).
5. **CSS Transformation Rendering:**
- Enforce `overflow: hidden` on the outer image container boundary.
- Apply inline styles to the inner container or the Image component: `transform: translate(${translateX}px, ${translateY}px) scale(${scale})`.
- Update cursor styles (`cursor: grab` natively, `cursor: grabbing` on drag).

---

## 4. Agentic Verification

Execute the following commands to validate the implementation:

1. **Type & Lint Pass:** Run standard formatting and type checks.

```bash
/lint src/app/transcribe/workspace/page.tsx
```

2. **Targeted Test Execution:**

```bash
/test-route src/app/transcribe/workspace/page.test.tsx
```
Loading