Skip to content

Commit 882c800

Browse files
committed
feat: upgraded to next 15 and tailwind 4
1 parent 2961b2d commit 882c800

24 files changed

+5204
-5214
lines changed

.npmrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
shamefully-hoist=true
2+
node-linker=hoisted
3+
auto-install-peers=true
4+
strict-peer-dependencies=false

CLAUDE.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Clover Map is a Nuxt 4 project management application that visualizes workflows using interactive treemap visualizations. It's a client-side application with no backend database - all data is stored locally in the browser using Pinia's persisted state.
8+
9+
## Commands
10+
11+
### Development
12+
- `pnpm run dev` - Start the development server at http://localhost:3000
13+
- `pnpm run build` - Build the application for production
14+
- `pnpm run start` - Start production server
15+
16+
### Testing
17+
- `pnpm run test` - Run unit tests with Vitest in watch mode
18+
- `pnpm run test:coverage` - Run tests with coverage report
19+
- `pnpm run test:e2e` - Run Playwright E2E tests
20+
- `pnpm run test:precommit` - Run tests once (used by pre-commit hooks)
21+
22+
### Code Quality
23+
- `pnpm run lint` - Run ESLint on all files
24+
- `pnpm run lint --fix` - Auto-fix linting issues
25+
26+
## Architecture
27+
28+
### Tech Stack
29+
- **Framework**: Nuxt 4.1.3 with Vue 3.5.7 composition API
30+
- **Styling**: Tailwind CSS v4 with dark mode support
31+
- **State Management**: Pinia v3 with pinia-plugin-persistedstate (browser storage)
32+
- **Testing**: Vitest v3 for unit tests, Playwright for E2E tests
33+
- **Package Manager**: pnpm
34+
35+
### Core Data Model
36+
The application revolves around three main types (defined in `types/index.ts`):
37+
- **Project**: Contains sections and custom statuses
38+
- **Section**: Hierarchical tree nodes with key, name, optional status, and children
39+
- **Status**: Customizable workflow states with name and color
40+
41+
### Application Structure
42+
- **app/components/** - Vue components organized by feature
43+
- `TreeMap.vue` and `TreeNode.vue` - Core treemap visualization
44+
- `ProjectPanel.vue` - Sidebar project management interface
45+
- `config/` - Configuration drawer components for status and view settings
46+
- `project/` - Project creation and editing components
47+
- `ui/` - Reusable UI components
48+
- **app/composables/** - Shared logic and Pinia store
49+
- `store.ts` - Central Pinia store for all application state
50+
- Other composables provide feature-specific logic
51+
- **app/pages/** - Nuxt page routing
52+
- `index.vue` - Main project list
53+
- `projects/[id].vue` - Individual project treemap view
54+
55+
### State Management
56+
The entire application state is managed through a single Pinia store (`app/composables/store.ts`) that:
57+
- Manages projects, sections, and their relationships
58+
- Handles UI state (panel collapse, dark mode, view settings)
59+
- Persists all data to browser storage automatically
60+
- Maintains a parent-child mapping for efficient tree navigation
61+
62+
### Key Features Implementation
63+
- **Treemap Visualization**: Recursive components render hierarchical data with dynamic sizing based on viewport settings
64+
- **Drag & Drop**: Native HTML5 drag and drop for reorganizing sections
65+
- **Status Management**: Customizable statuses with color coding that cycle on click
66+
- **Data Persistence**: All changes automatically save to browser storage via Pinia plugin
67+
- **Import/Export**: JSON file upload/download for project backup and sharing

app/assets/css/tailwind.css

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* assets/css/tailwind.css */
2-
@tailwind base;
3-
@tailwind components;
4-
@tailwind utilities;
2+
@import "tailwindcss";
3+
4+
/* Enable class-based dark mode in Tailwind v4 */
5+
@custom-variant dark (&:where(.dark, .dark *));
56

67
body {
78
font-family: 'Nunito', sans-serif;

app/components/ProjectPanel.vue

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ onMounted(() => {
229229
</span>
230230
</div>
231231
<button
232-
class="text-rose-500 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-600 transition-colors duration-200 ml-2 flex-shrink-0"
232+
class="text-rose-500 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-600 transition-colors duration-200 ml-2 flex-shrink-0 cursor-pointer"
233233
aria-label="Delete project"
234234
@click="initiateDelete($event, project.id)"
235235
>
@@ -258,7 +258,7 @@ onMounted(() => {
258258
class="flex-grow flex flex-col items-center space-y-4 p-2"
259259
>
260260
<button
261-
class="w-10 h-10 rounded-full flex items-center justify-center bg-stone-300 hover:bg-stone-400 dark:bg-stone-500 dark:hover:bg-stone-400 text-stone-800 dark:text-stone-100 transition-colors duration-200"
261+
class="w-10 h-10 rounded-full flex items-center justify-center bg-stone-300 hover:bg-stone-400 dark:bg-stone-500 dark:hover:bg-stone-400 text-stone-800 dark:text-stone-100 transition-colors duration-200 cursor-pointer"
262262
@click="openAddProjectModal"
263263
>
264264
<svg
@@ -279,7 +279,7 @@ onMounted(() => {
279279
<button
280280
v-for="project in sortedProjects"
281281
:key="project.id"
282-
class="w-10 h-10 rounded-full flex items-center justify-center transition-colors duration-200"
282+
class="w-10 h-10 rounded-full flex items-center justify-center transition-colors duration-200 cursor-pointer"
283283
:class="[
284284
project.id === currentProjectId
285285
? 'bg-stone-300 dark:bg-stone-500 text-stone-800 dark:text-stone-100'
@@ -421,7 +421,7 @@ onMounted(() => {
421421
<button
422422
v-for="(action, index) in ['configure', 'upload', 'export', 'home']"
423423
:key="index"
424-
class="w-full p-2 rounded flex items-center justify-center bg-stone-200 hover:bg-stone-300 dark:bg-stone-600 dark:hover:bg-stone-500 text-stone-700 dark:text-stone-200 transition-colors duration-200"
424+
class="w-full p-2 rounded flex items-center justify-center bg-stone-200 hover:bg-stone-300 dark:bg-stone-600 dark:hover:bg-stone-500 text-stone-700 dark:text-stone-200 transition-colors duration-200 cursor-pointer"
425425
@click="handleAction(action)"
426426
>
427427
<svg
@@ -485,13 +485,13 @@ onMounted(() => {
485485
</p>
486486
<div class="flex justify-end space-x-2">
487487
<button
488-
class="px-4 py-2 bg-stone-300 text-stone-800 rounded hover:bg-stone-400 transition-colors duration-200"
488+
class="px-4 py-2 bg-stone-300 text-stone-800 rounded hover:bg-stone-400 transition-colors duration-200 cursor-pointer"
489489
@click="cancelDelete"
490490
>
491491
Cancel
492492
</button>
493493
<button
494-
class="px-4 py-2 bg-rose-500 text-white rounded hover:bg-rose-600 transition-colors duration-200"
494+
class="px-4 py-2 bg-rose-500 text-white rounded hover:bg-rose-600 transition-colors duration-200 cursor-pointer"
495495
@click="confirmDeleteProject"
496496
>
497497
Delete
@@ -507,7 +507,7 @@ onMounted(() => {
507507
>
508508
<div class="bg-white dark:bg-stone-800 p-6 rounded-lg shadow-xl w-full max-w-lg relative">
509509
<button
510-
class="absolute top-4 right-4 text-stone-400 hover:text-stone-600 dark:text-stone-300 dark:hover:text-white transition-colors duration-300"
510+
class="absolute top-4 right-4 text-stone-400 hover:text-stone-600 dark:text-stone-300 dark:hover:text-white transition-colors duration-300 cursor-pointer"
511511
aria-label="Close Modal"
512512
@click="closeAddProjectModal"
513513
>

app/components/TreeMap.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ConfigDrawer from '~/components/config/ConfigDrawer.vue'
66
import { useStore } from '~/composables/store'
77
import { useProjects } from '~/composables/project'
88
9-
const chart = ref < HTMLElement | null > (null)
9+
const chart = ref <HTMLElement | null> (null)
1010
const store = useStore()
1111
const { currentProject, renameProject } = useProjects()
1212
@@ -61,7 +61,7 @@ const cancelEdit = () => {
6161
@keyup.esc="cancelEdit"
6262
>
6363
<button
64-
class="ml-2 text-teal-600 hover:text-teal-700 dark:text-teal-400 dark:hover:text-teal-300"
64+
class="ml-2 text-teal-600 hover:text-teal-700 dark:text-teal-400 dark:hover:text-teal-300 cursor-pointer"
6565
aria-label="Save project name"
6666
@click="saveEdit"
6767
>
@@ -82,7 +82,7 @@ const cancelEdit = () => {
8282
</button>
8383

8484
<button
85-
class="ml-2 text-rose-600 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-300"
85+
class="ml-2 text-rose-600 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-300 cursor-pointer"
8686
aria-label="Cancel editing"
8787
@click="cancelEdit"
8888
>
@@ -107,7 +107,7 @@ const cancelEdit = () => {
107107
<span @dblclick="startEditing">{{ currentProject.name }}</span>
108108

109109
<button
110-
class="ml-2 text-stone-600 hover:text-stone-700 dark:text-stone-300 dark:hover:text-stone-200"
110+
class="ml-2 text-stone-600 hover:text-stone-700 dark:text-stone-300 dark:hover:text-stone-200 cursor-pointer"
111111
aria-label="Edit project name"
112112
@click="startEditing"
113113
>

app/components/TreeNode.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ onMounted(() => {
328328
class="flex ml-4 space-x-2"
329329
>
330330
<button
331-
class="p-1 rounded-full bg-black/10 dark:bg-black/10"
331+
class="p-1 rounded-full bg-black/10 dark:bg-black/10 cursor-pointer"
332332
@click.stop="addSiblingNode"
333333
>
334334
<svg
@@ -349,7 +349,7 @@ onMounted(() => {
349349
</button>
350350

351351
<button
352-
class="p-1 rounded-full bg-black/10 dark:bg-black/10"
352+
class="p-1 rounded-full bg-black/10 dark:bg-black/10 cursor-pointer"
353353
@click.stop="addChildNode"
354354
>
355355
<svg
@@ -371,7 +371,7 @@ onMounted(() => {
371371

372372
<button
373373
:disabled="store.sections.length === 1 && !store.hasParent(props.node.key)"
374-
class="p-1 rounded-full bg-black/10 dark:bg-black/10 disabled:opacity-40"
374+
class="p-1 rounded-full bg-black/10 dark:bg-black/10 disabled:opacity-40 cursor-pointer"
375375
@click.stop="deleteNode"
376376
>
377377
<svg

app/components/config/ConfigDrawer.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ onMounted(() => {
4444
>
4545
<template v-if="drawerMinimized">
4646
<button
47-
class="text-white rounded-full text-lg font-bold h-6 w-6 flex items-center justify-center"
47+
class="text-white rounded-full text-lg font-bold h-6 w-6 flex items-center justify-center cursor-pointer"
4848
aria-label="Expand config"
4949
@click="toggleMinimize"
5050
>
@@ -83,7 +83,7 @@ onMounted(() => {
8383
/>
8484

8585
<button
86-
class="text-stone-100 hover:text-white bg-stone-600 hover:bg-stone-500 rounded-full p-1 text-lg font-bold flex items-center justify-center transition duration-200"
86+
class="text-stone-100 hover:text-white bg-stone-600 hover:bg-stone-500 rounded-full p-1 text-lg font-bold flex items-center justify-center cursor-pointer transition duration-200"
8787
aria-label="Minimize config"
8888
@click="toggleMinimize"
8989
>

app/components/config/StatusManager.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ const closeColorPicker = () => {
203203
/>
204204
<button
205205
:disabled="safeStatuses.length <= 1"
206-
class="p-1 rounded-full bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 hover:bg-stone-300 dark:hover:bg-stone-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-stone-300"
206+
class="p-1 rounded-full bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 hover:bg-stone-300 dark:hover:bg-stone-600 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-stone-300"
207207
aria-label="Remove status"
208208
@click="removeStatus(index)"
209209
>
@@ -248,7 +248,7 @@ const closeColorPicker = () => {
248248
/>
249249
<button
250250
:disabled="!newStatus.name || !newStatus.color"
251-
class="p-1 rounded-full bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 hover:bg-stone-300 dark:hover:bg-stone-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-stone-300"
251+
class="p-1 rounded-full bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 hover:bg-stone-300 dark:hover:bg-stone-600 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-stone-300"
252252
aria-label="Add new status"
253253
@click="addNewStatus"
254254
>
@@ -281,7 +281,7 @@ const closeColorPicker = () => {
281281
Choose a Color
282282
</h3>
283283
<button
284-
class="text-stone-400 hover:text-stone-600 dark:text-stone-300 dark:hover:text-white transition-colors duration-300"
284+
class="text-stone-400 hover:text-stone-600 dark:text-stone-300 dark:hover:text-white cursor-pointer transition-colors duration-300"
285285
aria-label="Close Color Picker"
286286
@click="closeColorPicker"
287287
>
@@ -306,7 +306,7 @@ const closeColorPicker = () => {
306306
<button
307307
v-for="color in pastelColors"
308308
:key="color"
309-
class="w-full pt-full rounded-full border border-stone-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-stone-400 transition-transform duration-200 hover:scale-110"
309+
class="w-full pt-full rounded-full border border-stone-200 cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-stone-400 transition-transform duration-200 hover:scale-110"
310310
:style="{ backgroundColor: color }"
311311
:aria-label="`Select color ${color}`"
312312
@click="updateStatusColor(color)"

app/components/project/ProjectForm.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const loadConfigFromUserInput = async () => {
3434
await loadFromUserInput(jsonPreview.value)
3535
sampleError.value = ''
3636
}
37-
catch (error) {
37+
catch {
3838
sampleError.value = 'Invalid JSON format'
3939
}
4040
finally {

app/components/project/RecursiveSection.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const removeNode = () => {
7272

7373
<div class="flex space-x-1">
7474
<button
75-
class="text-stone-500 hover:text-stone-600 dark:text-stone-400 dark:hover:text-stone-200 transition-colors duration-300 p-1"
75+
class="text-stone-500 hover:text-stone-600 dark:text-stone-400 dark:hover:text-stone-200 transition-colors duration-300 p-1 cursor-pointer"
7676
@click="addSibling"
7777
>
7878
<svg
@@ -91,7 +91,7 @@ const removeNode = () => {
9191
</button>
9292

9393
<button
94-
class="text-stone-500 hover:text-stone-600 dark:text-stone-400 dark:hover:text-stone-200 transition-colors duration-300 p-1"
94+
class="text-stone-500 hover:text-stone-600 dark:text-stone-400 dark:hover:text-stone-200 transition-colors duration-300 p-1 cursor-pointer"
9595
@click="addChild"
9696
>
9797
<svg
@@ -112,7 +112,7 @@ const removeNode = () => {
112112
</button>
113113

114114
<button
115-
class="text-stone-500 hover:text-stone-600 dark:text-stone-400 dark:hover:text-stone-200 transition-colors duration-300 p-1"
115+
class="text-stone-500 hover:text-stone-600 dark:text-stone-400 dark:hover:text-stone-200 transition-colors duration-300 p-1 cursor-pointer"
116116
@click="removeNode"
117117
>
118118
<svg

0 commit comments

Comments
 (0)