diff --git a/.husky/post-merge b/.husky/post-merge new file mode 100755 index 0000000..4e0f1c9 --- /dev/null +++ b/.husky/post-merge @@ -0,0 +1,16 @@ +#!/bin/sh + +changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)" + +if echo "$changed_files" | grep --quiet -E "package.json|package-lock.json"; then + echo "πŸ“¦ Dependencies changed. Syncing..." + + # --no-progress stops the terminal spam + # --loglevel error ensures we only see the bad stuff + if npm install --no-progress --loglevel error; then + echo "βœ… Node modules are up to date." + else + echo "❌ npm install failed! Please check your connection or package.json." + exit 1 + fi +fi diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..858555d --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,26 @@ +#!/bin/sh + +echo "πŸ” Running pre-commit checks..." + +# Get staged files (added, copied, modified) +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM) + +if [ -n "$STAGED_FILES" ]; then + echo "πŸ“ Checking file sizes..." + node .husky/scripts/check-file-size.js $STAGED_FILES + if [ $? -ne 0 ]; then + exit 1 + fi +fi + +# Generate project structure +echo "πŸ—ΊοΈ Updating project structure..." +node .husky/scripts/generate-project-tree.js +if [ $? -ne 0 ]; then + exit 1 +fi + +# Auto-add the generated project structure to the commit +git add docs/project-structure.txt + +echo "βœ… All pre-commit checks passed!" diff --git a/.husky/scripts/check-file-size.js b/.husky/scripts/check-file-size.js new file mode 100644 index 0000000..56a7f63 --- /dev/null +++ b/.husky/scripts/check-file-size.js @@ -0,0 +1,61 @@ +const fs = require('fs'); +const path = require('path'); +const MAX_LINES = 400; + +// List of file patterns to ignore (optional, can be customized) +const IGNORE_PATTERNS = [ + /\.lock$/, // Lock files + /\.min\.js$/, // Minified files + /\.map$/, // Source maps + /package-lock\.json$/, + /bun\.lock$/, + /tsconfig\.tsbuildinfo$/, + /\.md$/, // Markdown documentation files + /context\.md$/, // Context files for sub-agents + /project-structure\.txt$/, // Generated project structure +]; + +function shouldIgnore(filePath) { + return IGNORE_PATTERNS.some(pattern => pattern.test(filePath)); +} + +const files = process.argv.slice(2); +if (files.length === 0) { + console.log('ℹ️ No files to check'); + process.exit(0); +} + +let hasError = false; + +files.forEach(file => { + // Skip ignored patterns + if (shouldIgnore(file)) { + console.log(`⏭️ Skipping ${file} (ignored pattern)`); + return; + } + + // Check if file exists (it might have been deleted) + if (!fs.existsSync(file)) { + console.log(`⏭️ Skipping ${file} (file does not exist)`); + return; + } + + try { + const content = fs.readFileSync(file, 'utf8'); + const lines = content.split('\n').length; + + if (lines > MAX_LINES) { + console.error(`❌ ${file} is too large (${lines} lines, max ${MAX_LINES}). AI agents will struggle. Please refactor!`); + hasError = true; + } else { + console.log(`βœ… ${file} (${lines} lines) - OK`); + } + } catch (err) { + console.error(`⚠️ Error reading ${file}: ${err.message}`); + // Don't fail on read errors, just warn + } +}); + +if (hasError) { + process.exit(1); +} diff --git a/.husky/scripts/generate-project-tree.js b/.husky/scripts/generate-project-tree.js new file mode 100644 index 0000000..a1c5c78 --- /dev/null +++ b/.husky/scripts/generate-project-tree.js @@ -0,0 +1,108 @@ +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// Directory to start from (project root) +const ROOT_DIR = process.cwd(); +// Output file path +const OUTPUT_FILE = path.join(ROOT_DIR, 'docs', 'project-structure.txt'); + +// Function to check if a path is ignored by git +function isGitIgnored(filePath) { + try { + // git check-ignore -q returns 0 if ignored, 1 if not + execSync(`git check-ignore -q "${filePath}"`, { + cwd: ROOT_DIR, + stdio: 'ignore' + }); + return true; // Ignored + } catch (e) { + return false; // Not ignored + } +} + +// Function to generate tree structure +function generateTree(dir, prefix = '', isRoot = true) { + let structure = ''; + + // Add root directory name if it's the root + if (isRoot) { + structure += `${path.basename(dir)}/\n`; + } + + let items; + try { + items = fs.readdirSync(dir); + } catch (e) { + console.error(`Error reading directory ${dir}: ${e.message}`); + return structure; + } + + // Sort items: directories first, then files + const dirs = []; + const files = []; + + items.forEach(item => { + const itemPath = path.join(dir, item); + + // Skip if ignored by git + if (isGitIgnored(itemPath)) { + return; + } + + try { + const stat = fs.statSync(itemPath); + if (stat.isDirectory()) { + dirs.push(item); + } else { + files.push(item); + } + } catch (e) { + // Skip items we can't stat + } + }); + + // Sort directories and files alphabetically + dirs.sort(); + files.sort(); + + const allItems = [...dirs, ...files]; + + allItems.forEach((item, index) => { + const isLast = index === allItems.length - 1; + const connector = isLast ? '└── ' : 'β”œβ”€β”€ '; + const itemPath = path.join(dir, item); + + structure += `${prefix}${connector}${item}${dirs.includes(item) ? '/' : ''}\n`; + + // Recurse into directories + if (dirs.includes(item)) { + const newPrefix = prefix + (isLast ? ' ' : 'β”‚ '); + structure += generateTree(itemPath, newPrefix, false); + } + }); + + return structure; +} + +try { + console.log('πŸ—ΊοΈ Generating project structure...'); + + // Ensure docs directory exists + const docsDir = path.join(ROOT_DIR, 'docs'); + if (!fs.existsSync(docsDir)) { + fs.mkdirSync(docsDir, { recursive: true }); + console.log('πŸ“ Created docs directory'); + } + + // Generate tree + const tree = generateTree(ROOT_DIR, '', true); + + // Write to file + fs.writeFileSync(OUTPUT_FILE, tree); + console.log(`βœ… Project structure updated: ${OUTPUT_FILE}`); + +} catch (err) { + console.error(`❌ Error generating project structure: ${err.message}`); + process.exit(1); +} diff --git a/AGENTS.md b/AGENTS.md index aaba63b..5beb80d 100755 --- a/AGENTS.md +++ b/AGENTS.md @@ -120,6 +120,8 @@ src/ └── utils.ts # General utilities (cn function) ``` +*Note: A complete, up-to-date project tree is automatically generated on each commit and saved to `docs/project-structure.txt`. This file is generated by the pre-commit hook using `.husky/scripts/generate-project-tree.js` and respects `.gitignore` rules.* + ## Key Systems ### 0. Task 2 Completion Summary @@ -304,6 +306,27 @@ damage *= effects.myNewStatMultiplier; 3. **Add research skill in `constants.ts`** 4. **Map research to effect in `EFFECT_RESEARCH_MAPPING`** +## Git Hooks (Husky) + +This project uses **Husky** to manage git hooks for automated checks and agent assistance: + +### Pre-Commit Hook (`.husky/pre-commit`) +Runs automatically before each commit: +1. **File Size Check**: Ensures no staged file exceeds 400 lines (improves AI agent readability) +2. **Project Structure Generation**: Updates `docs/project-structure.txt` with current tree (respects `.gitignore`) + +### Post-Merge Hook (`.husky/post-merge`) +Runs after merging branches: +- Checks if `package.json` or `package-lock.json` changed +- Automatically runs `npm install` to sync dependencies + +### Implementation Files +- Hook scripts: `.husky/` directory +- File size check: `.husky/scripts/check-file-size.js` +- Tree generator: `.husky/scripts/generate-project-tree.js` + +--- + ## Common Pitfalls 1. **Forgetting to call `getUnifiedEffects()`**: Always use unified effects for stat calculations @@ -402,6 +425,9 @@ const useGameStore = create()( - **After Task 2**: `page.tsx` reduced from ~2554 to ~548 lines (78% reduction) - **After Task 2**: `store.ts` increased due to crafting-slice integration, but better organized +### Automated File Size Check +A pre-commit hook automatically checks all staged files. Files exceeding **400 lines** will be rejected. The hook runs via Husky and uses `.husky/scripts/check-file-size.js`. If your file is too large, refactor it into smaller modules before committing. + --- ## 🚫 BANNED CONTENT - NEVER ADD THESE diff --git a/docs/project-structure.txt b/docs/project-structure.txt index 808639f..122aca4 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -30,6 +30,7 @@ Mana-Loop/ β”‚ β”œβ”€β”€ objects/ β”‚ β”‚ β”œβ”€β”€ 00/ β”‚ β”‚ β”‚ β”œβ”€β”€ 89865f52503a4ed6ce8e44f85ad1548823d17f +β”‚ β”‚ β”‚ β”œβ”€β”€ af629716dd116c017ffcfcec42f231f25fc8f7 β”‚ β”‚ β”‚ └── df0433801bffaed8e862470cd9d877a392a0bf β”‚ β”‚ β”œβ”€β”€ 01/ β”‚ β”‚ β”‚ β”œβ”€β”€ 83373f92a07b4ca99d42064797d3883e67a299 @@ -69,6 +70,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 0e2686b4b2ef6886a68e0dc042996dfa7cfc6f β”‚ β”‚ β”‚ └── d7110257638a2d5d5edc62cfa9da4bac292d27 β”‚ β”‚ β”œβ”€β”€ 0a/ +β”‚ β”‚ β”‚ β”œβ”€β”€ 433c725df76a2755255866d16907c9986f4781 β”‚ β”‚ β”‚ β”œβ”€β”€ 5e710ea895a4dc8dc0dc3ce043306ffa76c239 β”‚ β”‚ β”‚ β”œβ”€β”€ 76fb517289fa32b9818769bf492160fcb04e0f β”‚ β”‚ β”‚ └── 86027f029d266e9624b71e2d4da5cd045bc0a4 @@ -85,6 +87,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ └── d75b80bc89dc1737e29fc4cf44f652efd7382e β”‚ β”‚ β”œβ”€β”€ 0e/ β”‚ β”‚ β”‚ β”œβ”€β”€ 44af05f746a329122b423966e616ecc9d880cb +β”‚ β”‚ β”‚ β”œβ”€β”€ 5111d1b3e03498dbfb5d6c67543a2593b19c73 β”‚ β”‚ β”‚ β”œβ”€β”€ 513309eaaaedd39fbc935f632b84e808f4a5c7 β”‚ β”‚ β”‚ β”œβ”€β”€ 6425c95064668170bd76995ebdfd98989632b0 β”‚ β”‚ β”‚ β”œβ”€β”€ a05052d51de061defbfa77a00dd1b530e33a70 @@ -187,6 +190,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ └── f2966be85f1216c437bec6a67b5786c8d3235c β”‚ β”‚ β”œβ”€β”€ 24/ β”‚ β”‚ β”‚ β”œβ”€β”€ 0aec75eea23003fd6d26f39380abc96de7faea +β”‚ β”‚ β”‚ β”œβ”€β”€ 50c7a463d43572a93ece21003b2838fbbb8694 β”‚ β”‚ β”‚ β”œβ”€β”€ b4cf1e08880fda17b4b5ce07271a6b7c5c5608 β”‚ β”‚ β”‚ └── db8f784221244600ab86c2648718af2eccb6c4 β”‚ β”‚ β”œβ”€β”€ 25/ @@ -204,6 +208,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 3544e7ab0cf747c14c43e244d769028812e8b3 β”‚ β”‚ β”‚ └── da1ccfc277949c75a70ca8fb7109d4fedae84d β”‚ β”‚ β”œβ”€β”€ 28/ +β”‚ β”‚ β”‚ β”œβ”€β”€ 2d4bbc5a89a3f3d5438ba115fe51b2fae55d67 β”‚ β”‚ β”‚ β”œβ”€β”€ 45b77c45e5c13af45bea3a4cb795fa50a3464b β”‚ β”‚ β”‚ └── 5de6345a053b7c7bad981a1cc6f2461e4b46d8 β”‚ β”‚ β”œβ”€β”€ 29/ @@ -288,7 +293,8 @@ Mana-Loop/ β”‚ β”‚ β”‚ └── 9d39f38428f63d1b92c3a6ea768714bb3710a4 β”‚ β”‚ β”œβ”€β”€ 39/ β”‚ β”‚ β”‚ β”œβ”€β”€ 508e799dd32d52064e3b11ba96bd6b5f7b445a -β”‚ β”‚ β”‚ └── ee95c04f1a5d27ca73b6e6d09b0f19d44dc050 +β”‚ β”‚ β”‚ β”œβ”€β”€ ee95c04f1a5d27ca73b6e6d09b0f19d44dc050 +β”‚ β”‚ β”‚ └── ff63ea469ee826c8fde88059b96cdfc9600dbd β”‚ β”‚ β”œβ”€β”€ 3a/ β”‚ β”‚ β”‚ β”œβ”€β”€ 3246f8b7541af387e748d41035f85c23258fdc β”‚ β”‚ β”‚ β”œβ”€β”€ 4a9bbe2b814564896931f84d54f32b9635772b @@ -396,6 +402,8 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 583f4ef4769141208222d7ab59add0dc5e057e β”‚ β”‚ β”‚ β”œβ”€β”€ 5973360217a5a0c5513b4231d7d7aecb5491dc β”‚ β”‚ β”‚ └── 80b8a67674b1ec24b0946807796de0bcc25865 +β”‚ β”‚ β”œβ”€β”€ 4e/ +β”‚ β”‚ β”‚ └── 0f1c944391f23057de9a41fb3025df77c55fa2 β”‚ β”‚ β”œβ”€β”€ 4f/ β”‚ β”‚ β”‚ β”œβ”€β”€ 03544eafd322a6fc08e7e3060b354112fa1dd6 β”‚ β”‚ β”‚ β”œβ”€β”€ 6959b82211e4f322fb4dfb076d11297ea89190 @@ -432,7 +440,8 @@ Mana-Loop/ β”‚ β”‚ β”‚ └── eac17f0ca858a8b819dbd5563cdcbbe868e0ee β”‚ β”‚ β”œβ”€β”€ 56/ β”‚ β”‚ β”‚ β”œβ”€β”€ 35711332332069bd1ea6bf8c4b515d2091d645 -β”‚ β”‚ β”‚ └── 3e41dbe3fa66bf37dd7fdb3841d25f3b63e1b4 +β”‚ β”‚ β”‚ β”œβ”€β”€ 3e41dbe3fa66bf37dd7fdb3841d25f3b63e1b4 +β”‚ β”‚ β”‚ └── a7f639893730faed9c87db0b32bd53b198301d β”‚ β”‚ β”œβ”€β”€ 57/ β”‚ β”‚ β”‚ └── 5c36d9b01d4de7c019bf4a567c660e45e59e43 β”‚ β”‚ β”œβ”€β”€ 58/ @@ -459,7 +468,8 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 81cc9e4f334986679aa0b5abd0d50f47cc90b3 β”‚ β”‚ β”‚ β”œβ”€β”€ b84bae8fba238223329506f97466e7cf5fa7b0 β”‚ β”‚ β”‚ β”œβ”€β”€ e045637a637062a848a01825e2cd649b72a6fe -β”‚ β”‚ β”‚ └── e52c7cd25543b9f8bf8bfa773155ade4c85142 +β”‚ β”‚ β”‚ β”œβ”€β”€ e52c7cd25543b9f8bf8bfa773155ade4c85142 +β”‚ β”‚ β”‚ └── eb80db0c59760a036a4cfd0d76ec0dccf9a547 β”‚ β”‚ β”œβ”€β”€ 5c/ β”‚ β”‚ β”‚ β”œβ”€β”€ 2ba11cb18dd30ce96066dc68b5b4a636c954b2 β”‚ β”‚ β”‚ β”œβ”€β”€ a57fa8b5e7f22e42b8eab270a5f0fb8943638d @@ -527,6 +537,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ f784695f332975747ab0433ffe1a44abf199ce β”‚ β”‚ β”‚ └── f899df9295233ec6453fbd6085ab737cdd4868 β”‚ β”‚ β”œβ”€β”€ 6b/ +β”‚ β”‚ β”‚ β”œβ”€β”€ 31a5c036630ed1f68ff328fa3b10648c4be095 β”‚ β”‚ β”‚ β”œβ”€β”€ 51408735e74d4fc645ae30843d713c57df7071 β”‚ β”‚ β”‚ β”œβ”€β”€ 6e5b13cdf380b8bf5de755155f6e907da7a61e β”‚ β”‚ β”‚ β”œβ”€β”€ 8b3486bc93f8e52d6b2e47d13a2a7dd23e2b07 @@ -556,7 +567,8 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 0b86d4d7fbadef15d4ebd2254cd0efeeb00f02 β”‚ β”‚ β”‚ β”œβ”€β”€ 5183cded881cf19d6f98a9d3ab06df6644c16a β”‚ β”‚ β”‚ β”œβ”€β”€ 86a8191ff528525e5aba6820cce68bbff546d8 -β”‚ β”‚ β”‚ └── dcbb75d5ff17ba47c9069128d7021411730057 +β”‚ β”‚ β”‚ β”œβ”€β”€ dcbb75d5ff17ba47c9069128d7021411730057 +β”‚ β”‚ β”‚ └── e17f665e36bf698ec4b16d499a3824f9299f33 β”‚ β”‚ β”œβ”€β”€ 70/ β”‚ β”‚ β”‚ β”œβ”€β”€ 286f121cdcab90944bd40dd7f87b27207a5be2 β”‚ β”‚ β”‚ β”œβ”€β”€ 56dc04d621a2da9a18615ddf93e74207a5c7f1 @@ -579,7 +591,8 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 4040d64067bea8a20f1ab1befadbf55e88c187 β”‚ β”‚ β”‚ β”œβ”€β”€ 5f1fb0cf9b4075a25d64d8dabc754ffe292f8a β”‚ β”‚ β”‚ β”œβ”€β”€ 9321d2cbb10a22eff108f2c478b6147b6053e8 -β”‚ β”‚ β”‚ └── 9c1991f12974b3eba86893dd274ae20ea07441 +β”‚ β”‚ β”‚ β”œβ”€β”€ 9c1991f12974b3eba86893dd274ae20ea07441 +β”‚ β”‚ β”‚ └── f9d0674966a673c6044c2ad2cfd9e4da092081 β”‚ β”‚ β”œβ”€β”€ 75/ β”‚ β”‚ β”‚ β”œβ”€β”€ 7130d618de424546c7a7b56f84199d12de4bb2 β”‚ β”‚ β”‚ β”œβ”€β”€ 863af2c6c9724bca37a9067c03f18fa8025407 @@ -604,7 +617,9 @@ Mana-Loop/ β”‚ β”‚ β”‚ └── ccc917fdf05df95c374e74b2fb621282423c4e β”‚ β”‚ β”œβ”€β”€ 79/ β”‚ β”‚ β”‚ β”œβ”€β”€ 4e834147c25b9f95eeeb0a034ed304d59eb65b -β”‚ β”‚ β”‚ └── 6dd6d47c5dae286ecfd9a37f2247bd66bc8241 +β”‚ β”‚ β”‚ β”œβ”€β”€ 6dd6d47c5dae286ecfd9a37f2247bd66bc8241 +β”‚ β”‚ β”‚ β”œβ”€β”€ e0c74973caa1b70808fd5289a41fb8bd9823d8 +β”‚ β”‚ β”‚ └── f55a91b03a0be6732e1b1b491c7118c80f945d β”‚ β”‚ β”œβ”€β”€ 7a/ β”‚ β”‚ β”‚ β”œβ”€β”€ 8a2b7b540f5a728563ef3d12598fea3477ad53 β”‚ β”‚ β”‚ β”œβ”€β”€ b82229865f4bfeb9ee986c0b8bb9cc35822d93 @@ -632,6 +647,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ └── abe59cc2483a0ddb8e5e1aacd333bc1442fba4 β”‚ β”‚ β”œβ”€β”€ 80/ β”‚ β”‚ β”‚ β”œβ”€β”€ 4d76e2372cb0762d0d5a386312743a16129aaa +β”‚ β”‚ β”‚ β”œβ”€β”€ 8639f4858f4556f65d2abe08fa6b256a5d8f89 β”‚ β”‚ β”‚ β”œβ”€β”€ e5aa7b866e6c9409d98f144b364929aa6e5d34 β”‚ β”‚ β”‚ └── f63e03a3c4c816ab638943eeb4528e8f31b967 β”‚ β”‚ β”œβ”€β”€ 81/ @@ -655,6 +671,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ └── f1872bee2f7c9a91ac5f2467b2529aa4568217 β”‚ β”‚ β”œβ”€β”€ 85/ β”‚ β”‚ β”‚ β”œβ”€β”€ 262a3c51bf450062054385ec4bf239f00323ec +β”‚ β”‚ β”‚ β”œβ”€β”€ 8555d2b8e21ed1663584937f1270200088cef7 β”‚ β”‚ β”‚ └── 9efc3d27e0e6b5ed91a9c10f4d0604eda3486c β”‚ β”‚ β”œβ”€β”€ 86/ β”‚ β”‚ β”‚ β”œβ”€β”€ 47ecac91e1311dac4b642d58f12b1ce7200a7d @@ -663,6 +680,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ d109e356e178eed0db83c51eab10e61ea8b2bf β”‚ β”‚ β”‚ └── f668e0697cd495803457545dc4c4bcb0984aee β”‚ β”‚ β”œβ”€β”€ 88/ +β”‚ β”‚ β”‚ β”œβ”€β”€ d6016557cec6d8a77df16e9952246052cc5839 β”‚ β”‚ β”‚ └── e8cd0873832532404e8add60bff3021876a4ca β”‚ β”‚ β”œβ”€β”€ 89/ β”‚ β”‚ β”‚ β”œβ”€β”€ 622d12bf59536536bb5a2720423f2b23c43ec3 @@ -754,6 +772,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ └── e9ad439342d53781cb100f042a8765883ebd7c β”‚ β”‚ β”œβ”€β”€ 9e/ β”‚ β”‚ β”‚ β”œβ”€β”€ 50b42df6804da2182069b2ec3924017860b421 +β”‚ β”‚ β”‚ β”œβ”€β”€ 672cec517992f90eb140f794cb3e1fd5687f5c β”‚ β”‚ β”‚ └── 899d667e232e60a14b57f1608551abe3b53ee8 β”‚ β”‚ β”œβ”€β”€ 9f/ β”‚ β”‚ β”‚ β”œβ”€β”€ 029d93e1a36fb94d980c59b50d1ece90b78e4b @@ -769,6 +788,7 @@ Mana-Loop/ β”‚ β”‚ β”œβ”€β”€ a1/ β”‚ β”‚ β”‚ β”œβ”€β”€ 596a04fee80d51dd192d438c1a9956f6baae1c β”‚ β”‚ β”‚ β”œβ”€β”€ 601b8fa106b067c9c8168ba5e33d62c1e32884 +β”‚ β”‚ β”‚ β”œβ”€β”€ c5c7892060a62ec535bba1dc074d4aab099a24 β”‚ β”‚ β”‚ └── ce17196dacab0a9e67e34f576e1bf9649c0112 β”‚ β”‚ β”œβ”€β”€ a2/ β”‚ β”‚ β”‚ β”œβ”€β”€ 125a21ae91abbc2b229881e77adc8de8bb1119 @@ -779,6 +799,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 06e50d3c7cdafd5290a5259638db6aacdb3231 β”‚ β”‚ β”‚ β”œβ”€β”€ 15020820665e5cd0e83f5abd16eb0f7c7af59b β”‚ β”‚ β”‚ β”œβ”€β”€ 38a29fcffd8b6ed2554a7e4eae7c0bf3130424 +β”‚ β”‚ β”‚ β”œβ”€β”€ 824c68a25945963efcbdbde55a86cebea43454 β”‚ β”‚ β”‚ └── 9d4f0ef1c79b402210fb92d711b8cd1bb56d63 β”‚ β”‚ β”œβ”€β”€ a5/ β”‚ β”‚ β”‚ β”œβ”€β”€ 28feb8e27f2d1484bbf56fa98235aa89dda32c @@ -792,6 +813,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 0cc68fb3b23eac335805e864eaa822e59430cd β”‚ β”‚ β”‚ β”œβ”€β”€ 265b055632e407d5acc83fc57c245c4762c56b β”‚ β”‚ β”‚ β”œβ”€β”€ 8c72da042ab31e7a3c1d0c61027b8e2a15946a +β”‚ β”‚ β”‚ β”œβ”€β”€ 98115932b3d0e9fbb6e53d6ea30702cb493529 β”‚ β”‚ β”‚ β”œβ”€β”€ b91225ec8ce3af2529715acfddafbd2b573f46 β”‚ β”‚ β”‚ └── e587bb0946929a79a3130518751241b3bcf97c β”‚ β”‚ β”œβ”€β”€ a8/ @@ -802,6 +824,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ dfe6258ae5c081fbe9a9e5c6cef67f2b9692a5 β”‚ β”‚ β”‚ └── f5fa002ee2a633659d76b11ee3cf3c4312c082 β”‚ β”‚ β”œβ”€β”€ a9/ +β”‚ β”‚ β”‚ β”œβ”€β”€ 46e3b6aaa9e3e200465467235ee8a98a9b9b61 β”‚ β”‚ β”‚ β”œβ”€β”€ 4d2ca0680551c9e48836e882b4287627eef295 β”‚ β”‚ β”‚ β”œβ”€β”€ 7c1a2ab27a9500547b545b82760391cc566e21 β”‚ β”‚ β”‚ └── da14f29a95fe06481330a240259ad97266c001 @@ -909,6 +932,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 8ed0f92cb1fb67c7c2757697c36c969c0361c2 β”‚ β”‚ β”‚ └── 94586f56c1f38f1bf58f2acb6db0e2f1f22533 β”‚ β”‚ β”œβ”€β”€ bc/ +β”‚ β”‚ β”‚ β”œβ”€β”€ 0fdf0416b50197ff719252a7dcc9dd923c28c5 β”‚ β”‚ β”‚ β”œβ”€β”€ 21c14176f27a1b89c57858fa10efaae88cbc97 β”‚ β”‚ β”‚ β”œβ”€β”€ 312ed47776862d880a89b4271b6596cfdf3641 β”‚ β”‚ β”‚ └── b5e902f9a50a1baa5619c5f1288e8458f41f0f @@ -943,7 +967,8 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 5e6920066307295c3619317dec8642423bba4e β”‚ β”‚ β”‚ β”œβ”€β”€ 8b734bc40f9c3bfc6d2cf85c16e212a0e6e482 β”‚ β”‚ β”‚ β”œβ”€β”€ 9ae605d632bf8a76788af5f02310b1ee2a2e5d -β”‚ β”‚ β”‚ └── b5f158618b61280eec85677f79347fb248b4b4 +β”‚ β”‚ β”‚ β”œβ”€β”€ b5f158618b61280eec85677f79347fb248b4b4 +β”‚ β”‚ β”‚ └── e9f8576c45c8a6975b91858f63ef73afd82e06 β”‚ β”‚ β”œβ”€β”€ c4/ β”‚ β”‚ β”‚ β”œβ”€β”€ 481f618950447b053639d3e46b4e8df430c5be β”‚ β”‚ β”‚ β”œβ”€β”€ 4af1fe7699ee609a9c8dfea201b2cba349fe0b @@ -1005,7 +1030,8 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 138d78652bd3a2e13b19623b27e123b3387915 β”‚ β”‚ β”‚ β”œβ”€β”€ 7b0dc137be02425724f3fcde8d298ed3141aa2 β”‚ β”‚ β”‚ β”œβ”€β”€ 8872e8032bae8afcf60220279a7da3c82725c7 -β”‚ β”‚ β”‚ └── 9981f3ad69bb4819f040597c91eae14dc2a459 +β”‚ β”‚ β”‚ β”œβ”€β”€ 9981f3ad69bb4819f040597c91eae14dc2a459 +β”‚ β”‚ β”‚ └── ee77a4f340fdbdf973a8c6e9e3cb1dfa77f350 β”‚ β”‚ β”œβ”€β”€ cf/ β”‚ β”‚ β”‚ β”œβ”€β”€ 795db7e988a37b31cef9e54940209c2d84d9c1 β”‚ β”‚ β”‚ β”œβ”€β”€ e561cbc6da79d0158c5c275f5c4bb9f6fb8b82 @@ -1020,13 +1046,15 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 1e2f0d1cba4887db91cd104b16583bf8a00542 β”‚ β”‚ β”‚ β”œβ”€β”€ 3c55f27c2a512dc84c8d0ddb7768167decd088 β”‚ β”‚ β”‚ β”œβ”€β”€ 54d7be0517d636132d32332162b310b02f7f90 +β”‚ β”‚ β”‚ β”œβ”€β”€ 90afa6d553099c5f4fce7dea54a26033cfecf7 β”‚ β”‚ β”‚ └── fcf1fc21915c4c6bd9460506e4ebe41e2ec556 β”‚ β”‚ β”œβ”€β”€ d2/ β”‚ β”‚ β”‚ β”œβ”€β”€ 3705894748304c5e9220b7a85d693498564d4b β”‚ β”‚ β”‚ └── 7f11576582712b3494db9ed41d7e97455e78d0 β”‚ β”‚ β”œβ”€β”€ d3/ β”‚ β”‚ β”‚ β”œβ”€β”€ 9eb8296bc4d58c26172dd460373a64f36034c2 -β”‚ β”‚ β”‚ └── c69322412a4816c6af9ec3bf8b529619300d8b +β”‚ β”‚ β”‚ β”œβ”€β”€ c69322412a4816c6af9ec3bf8b529619300d8b +β”‚ β”‚ β”‚ └── f7f494ed92048a25651d724690387633cc60c6 β”‚ β”‚ β”œβ”€β”€ d4/ β”‚ β”‚ β”‚ β”œβ”€β”€ 97d2ee7e845dfa7774a41e20fbbb8be16d942e β”‚ β”‚ β”‚ β”œβ”€β”€ 9f55d93bc595604f4d79a42795e4f537487522 @@ -1193,6 +1221,7 @@ Mana-Loop/ β”‚ β”‚ β”‚ β”œβ”€β”€ 520e15b8a46087c8b64798d277ca3ea032c79e β”‚ β”‚ β”‚ β”œβ”€β”€ 754e2dec9d0f324442a65c7cdc21b5ced1a222 β”‚ β”‚ β”‚ β”œβ”€β”€ 8a2d260f95779857064c2e7c4837eb6f260002 +β”‚ β”‚ β”‚ β”œβ”€β”€ b618e095fd23c24f7364578165a0ef83318650 β”‚ β”‚ β”‚ └── dd633525609ba758e8f2a0ba41e30ea49bdac4 β”‚ β”‚ β”œβ”€β”€ f9/ β”‚ β”‚ β”‚ β”œβ”€β”€ 664a28797bc0099963b4d2317d912336ce9c8a @@ -1270,6 +1299,7 @@ Mana-Loop/ β”‚ β”‚ β”œβ”€β”€ subtask_12_context.md β”‚ β”‚ └── subtask_13_context.md β”‚ β”œβ”€β”€ GAME_BRIEFING.md +β”‚ β”œβ”€β”€ project-structure.txt β”‚ β”œβ”€β”€ skills.md β”‚ └── task5.md β”œβ”€β”€ download/ @@ -1499,6 +1529,5 @@ Mana-Loop/ β”œβ”€β”€ package.json β”œβ”€β”€ postcss.config.mjs β”œβ”€β”€ tailwind.config.ts -β”œβ”€β”€ test-small.ts β”œβ”€β”€ tsconfig.json └── vitest.config.ts diff --git a/docs/task5.md b/docs/task5.md index 6b6e5b1..39ff63e 100644 --- a/docs/task5.md +++ b/docs/task5.md @@ -2,69 +2,70 @@ ## Status Overview - **Start Date**: 2025-05-19 -- **Current Phase**: PRIORITY 2 (Spire Mode Fixes) -- **Overall Progress**: 21% complete (4/19 tasks done) +- **Current Phase**: PRIORITY 3 (UI/UX Restructuring) +- **Overall Progress**: 42% complete (8/19 tasks done) --- ## PRIORITY 0 β€” Crashes (Fix First, Parallel) βœ… COMPLETED | Task | Status | Notes | |------|--------|-------| -| SpellsTab crash diagnosis/fix | Completed | Fixed unprotected ENCHANTMENT_EFFECTS access, added spell.cost guards | -| LabTab crash diagnosis/fix | Completed | Added safe access to store.elements with `|| {}` fallbacks | -| DebugTab crash diagnosis/fix | Completed | Moved Toaster/GameToaster inside DebugProvider in layout.tsx | +| SpellsTab crash diagnosis/fix | Completed | Fixed unprotected ENCHANTMENT_EFFECTS access | +| LabTab crash diagnosis/fix | Completed | Added safe access to store.elements | +| DebugTab crash diagnosis/fix | Completed | Moved Toaster/GameToaster inside DebugProvider | --- ## PRIORITY 1 β€” Mana Conversion Mechanic Fix βœ… COMPLETED | Task | Status | Notes | |------|--------|-------| -| Wire conversion drain to effectiveRegen instead of active mana pool | Completed | Removed redundant `rawMana -= actualConversion` in store.ts since effectiveRegen already accounts for conversion drain | +| Wire conversion drain to effectiveRegen | Completed | Removed redundant rawMana -= actualConversion | --- ## PRIORITY 2 β€” Spire Mode Fixes | Task | Status | Notes | |------|--------|-------| -| 2a. Floor Rendering & Identity (type, named enemy, special properties) | Pending | | -| 2b. Swarm Floors (show multiple enemies, verify generation) | Pending | | -| 2c. HP Bar Live Updates | Pending | | -| 2d. Casting Progress Overflow Fix | Pending | | -| 2e. Climb/Descend Controls (spam fix, re-entry resume, button rename) | Pending | | -| 2f. Activity Log Implementation | Pending | | -| 2g. Spell Info Display Fix (dmg/cast vs DPS) | Pending | | +| 2a. Floor Rendering & Identity | Pending | Context gathering next | +| 2b. Swarm Floors | βœ… Completed | Verified by check sub-agent | +| 2c. HP Bar Live Updates | βœ… Completed | floorHP synced to enemy HP | +| 2d. Casting Progress Overflow | Pending | Context gathering next | +| 2e. Climb/Descend Controls | Pending | Context gathering next | +| 2f. Activity Log Implementation | Pending | Context gathering next | +| 2g. Spell Info Display Fix | Pending | Context gathering next | --- ## PRIORITY 3 β€” UI/UX Restructuring | Task | Status | Notes | |------|--------|-------| -| 3a. CraftingTab Restructure (remove 1-4 bar, split Fabricate/Enchant, top sub-tabs) | Pending | | -| 3b. LootTab Nesting Fix (remove redundant layers) | Pending | | -| 3c. AchievementsTab Nesting Fix (remove duplicate headings) | Pending | | +| 3a. CraftingTab Restructure | βœ… Completed | Removed stepper, added Fabricate/Enchant tabs | +| 3b. LootTab Nesting Fix | βœ… Completed | Removed redundant LootTab wrapper | +| 3c. AchievementsTab Nesting Fix | In Progress | Context gathering β†’ execution | --- ## PRIORITY 4 β€” Enchantment Effects & Research | Task | Status | Notes | |------|--------|-------| -| 4a. Mana-Type Capacity Enchantment Effects | Pending | Per unlocked mana type | -| 4b. Mana Capacity Research Visibility Gate | Pending | Only show if mana type unlocked | -| 4c. Skill Requirement Display Bug Fix (undefined Lv.[object Object]) | Pending | | -| 4d. Enchantment Power Effect Implementation + Stub Audit | Pending | Replace placeholder, audit all stubs | +| 4a. Mana-Type Capacity Enchantment Effects | Pending | Context gathering next | +| 4b. Mana Capacity Research Visibility Gate | Pending | Context gathering next | +| 4c. Skill Requirement Display Bug Fix | Pending | Context gathering next | +| 4d. Enchantment Power Effect Implementation | Pending | Partially done | --- ## PRIORITY 5 β€” Insight Upgrade Analysis | Task | Status | Notes | |------|--------|-------| -| 5a. Create design proposal in docs/task5_insight_proposals.md | Pending | Wait for human sign-off | +| 5a. Create design proposal | Pending | Context gathering next | --- -## Notes & Decisions -- βœ… PRIORITY 0 crashes fixed via parallel sub-agents, verified and applied all fixes -- βœ… PRIORITY 1 mana conversion fix applied: removed double-counting of conversion drain in store.ts tick logic -- Next steps: Dispatch parallel sub-agents for PRIORITY 2 Spire Mode fixes (2a-2g) -- Advisor tool will be used for ambiguous design decisions -- Sub-agent instructions passed via inline prompts with full context +## Workflow Log +- βœ… PRIORITY 0 crashes fixed via parallel sub-agents +- βœ… PRIORITY 1 mana conversion fix applied +- βœ… PRIORITY 2b, 2c verified completed +- βœ… Task 12 (CraftingTab) completed +- βœ… Task 13 (LootTab) completed +- ⏳ Current: Task 14 (AchievementsTab) context gathering diff --git a/docs/task5/subtask_12_context.md b/docs/task5/subtask_12_context.md new file mode 100644 index 0000000..a798115 --- /dev/null +++ b/docs/task5/subtask_12_context.md @@ -0,0 +1,451 @@ +# Task 12 Context: Restructure CraftingTab + +## Task Description +Restructure CraftingTab (remove 1-4 progress bar, split Fabricate/Enchant, top sub-tabs) (PRIORITY 3a) + +## Source Files + +### 1. `/src/components/game/tabs/CraftingTab.tsx` (268 lines) + +**Imports and Dependencies:** +```typescript +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Progress } from '@/components/ui/progress'; +import { GameCard } from '@/components/ui/game-card'; +import { SectionHeader } from '@/components/ui/section-header'; +import { ActionButton } from '@/components/ui/action-button'; +import { Stepper } from '@/components/ui/stepper'; +import { Scroll, Hammer, Sparkles, Anvil } from 'lucide-react'; +import type { EquipmentInstance, EnchantmentDesign, DesignEffect, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types'; +import { fmt, type GameStore } from '@/lib/game/store'; +import { + EnchantmentDesigner, + EnchantmentPreparer, + EnchantmentApplier, + EquipmentCrafter, +} from '@/components/game/crafting'; +import { useGameToast } from '@/components/game/GameToast'; +``` + +**Crafting Phases Constant:** +```typescript +// Crafting phases for the stepper +const CRAFTING_PHASES = ['Design', 'Prepare', 'Apply', 'Craft']; +``` + +**Component Props:** +```typescript +export interface CraftingTabProps { + store: GameStore; +} +``` + +**State and Stepper Mapping:** +```typescript +export function CraftingTab({ store }: CraftingTabProps) { + const showToast = useGameToast(); + const currentAction = store.currentAction; + const designProgress = store.designProgress; + const preparationProgress = store.preparationProgress; + const applicationProgress = store.applicationProgress; + const equipmentCraftingProgress = store.equipmentCraftingProgress; + const pauseApplication = store.pauseApplication; + const resumeApplication = store.resumeApplication; + + const [craftingStage, setCraftingStage] = useState<'design' | 'prepare' | 'apply' | 'craft'>('craft'); + + // Map crafting stage to stepper index + const getStepperIndex = (stage: string): number => { + switch (stage) { + case 'design': return 0; + case 'prepare': return 1; + case 'apply': return 2; + case 'craft': return 3; + default: return 0; + } + }; +``` + +**Stepper Component (lines 58-68):** +```tsx +{/* Visual Stepper - Requirement: show Design, Prepare, Apply phases as visual stepper */} + + + +``` + +**Stage Content Conditional Rendering (lines 71-97):** +```tsx +{/* Stage Content - Without unlabeled Tabs, using conditional rendering instead */} +
+ {craftingStage === 'craft' && ( + + )} + {craftingStage === 'design' && ( + {}} + selectedEffects={[]} + setSelectedEffects={() => {}} + designName={''} + setDesignName={() => {}} + selectedDesign={null} + setSelectedDesign={() => {}} + /> + )} + {craftingStage === 'prepare' && ( + {}} + /> + )} + {craftingStage === 'apply' && ( + {}} + selectedDesign={null} + setSelectedDesign={() => {}} + onEnchantmentApplied={handleEnchantmentApplied} + onCapacityExceeded={handleCapacityExceeded} + /> + )} +
+``` + +**Stage Navigation Buttons (lines 99-131):** +```tsx +{/* Stage Navigation Buttons */} + +
+ setCraftingStage('craft')} + className={craftingStage === 'craft' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} + > + + Craft + + setCraftingStage('design')} + className={craftingStage === 'design' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} + > + + Design + + setCraftingStage('prepare')} + className={craftingStage === 'prepare' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} + > + + Prepare + + setCraftingStage('apply')} + className={craftingStage === 'apply' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} + > + + Apply + +
+
+``` + +**Current Activity Indicators (Progress Bars to be removed - lines 133-236):** +```tsx +{/* Current Activity Indicator */} +{currentAction === 'craft' && equipmentCraftingProgress && ( + + + {safeToFixed(calcPercent(equipmentCraftingProgress.progress, equipmentCraftingProgress.required), 0)}% + + } + /> + + {/* ... more content ... */} + +)} + +{currentAction === 'design' && designProgress && ( + + {/* ... Progress bar and content ... */} + +)} + +{currentAction === 'prepare' && preparationProgress && ( + + {/* ... Progress bar and content ... */} + +)} + +{currentAction === 'enchant' && applicationProgress && ( + + {/* ... Progress bar and content ... */} + +)} +``` + +--- + +### 2. Files in `/src/components/game/crafting/` Directory + +| File | Size | Last Modified | +|------|------|---------------| +| `EnchantmentApplier.tsx` | 12,206 bytes | 1777364523 | +| `EnchantmentDesigner.tsx` | 19,568 bytes | 1777361558 | +| `EnchantmentPreparer.tsx` | 14,816 bytes | 1777365343 | +| `EquipmentCrafter.tsx` | 9,121 bytes | 1777205526 | +| `index.tsx` | 396 bytes | 1777028644 | + +**Barrel File (`index.tsx`):** +```typescript +// Barrel file for crafting components + +export { EnchantmentDesigner, type EnchantmentDesignerProps } from './EnchantmentDesigner'; +export { EnchantmentPreparer, type EnchantmentPreparerProps } from './EnchantmentPreparer'; +export { EnchantmentApplier, type EnchantmentApplierProps } from './EnchantmentApplier'; +export { EquipmentCrafter, type EquipmentCrafterProps } from './EquipmentCrafter'; +``` + +--- + +### 3. Stepper Component (`/src/components/ui/stepper.tsx`) + +**Interface:** +```typescript +interface StepperProps extends React.HTMLAttributes { + steps: string[]; + currentStep: number; // 0-indexed + orientation?: "horizontal" | "vertical"; +} +``` + +**Full Implementation (100 lines):** +```typescript +import * as React from "react"; +import { cn } from "@/lib/utils"; +import { Check, Circle, ArrowRight } from "lucide-react"; + +interface StepperProps extends React.HTMLAttributes { + steps: string[]; + currentStep: number; // 0-indexed + orientation?: "horizontal" | "vertical"; +} + +interface StepProps { + label: string; + stepNumber: number; + isActive: boolean; + isCompleted: boolean; + isLast: boolean; + orientation?: "horizontal" | "vertical"; +} + +const Step = ({ label, stepNumber, isActive, isCompleted, isLast, orientation = "horizontal" }: StepProps) => { + return ( +
+
+
+ {isCompleted ? ( + + ) : ( + {stepNumber} + )} +
+ + {label} + +
+ {!isLast && ( +
+ )} +
+ ); +}; + +export function Stepper({ steps, currentStep, orientation = "horizontal", className, ...props }: StepperProps) { + return ( +
+ {steps.map((step, index) => ( +
+ +
+ ))} +
+ ); +} +``` + +--- + +### 4. Current Sub-Tab/Navigation Implementation Details + +**Current Structure:** +The CraftingTab currently uses a **4-stage linear workflow** with: +1. A visual Stepper component showing phases: Design β†’ Prepare β†’ Apply β†’ Craft +2. Navigation buttons at the bottom to switch between stages +3. Conditional rendering of content based on `craftingStage` state + +**Current Stages:** +- `design` - EnchantmentDesigner component (Design enchantments) +- `prepare` - EnchantmentPreparer component (Prepare equipment) +- `apply` - EnchantmentApplier component (Apply enchantments) +- `craft` - EquipmentCrafter component (Craft equipment) + +**Issues to Address (Task Requirements):** +1. **Remove 1-4 progress bar** - The Stepper component (lines 58-68) needs to be removed +2. **Split Fabricate/Enchant** - Currently "Craft" (EquipmentCrafter) is mixed in with enchantment workflow. Need to split into: + - "Fabricate" tab - for EquipmentCrafter (crafting equipment) + - "Enchant" tab - for the Design β†’ Prepare β†’ Apply workflow +3. **Top sub-tabs** - Replace the bottom navigation buttons with proper top-level sub-tabs + +**Current Navigation Pattern:** +- State: `craftingStage` (useState with 4 possible values) +- Navigation: 4 ActionButtons at the bottom of the tab +- Visual indicator: Stepper at the top showing progress through phases + +**Suggested New Structure (for implementation):** +``` +CraftingTab +β”œβ”€β”€ Top Sub-Tabs: [Fabricate] [Enchant] +β”œβ”€β”€ Fabricate Content: EquipmentCrafter +└── Enchant Content: + β”œβ”€β”€ Sub-Navigation: [Design] [Prepare] [Apply] + β”œβ”€β”€ Design: EnchantmentDesigner + β”œβ”€β”€ Prepare: EnchantmentPreparer + └── Apply: EnchantmentApplier +``` + +--- + +### 5. Component Props Signatures + +**EquipmentCrafterProps:** +```typescript +export interface EquipmentCrafterProps { + store: GameStore; +} +``` + +**EnchantmentDesignerProps:** +```typescript +export interface EnchantmentDesignerProps { + store: GameStore; + selectedEquipmentType: string | null; + setSelectedEquipmentType: (type: string | null) => void; + selectedEffects: DesignEffect[]; + setSelectedEffects: (effects: DesignEffect[]) => void; + designName: string; + setDesignName: (name: string) => void; + selectedDesign: string | null; + setSelectedDesign: (id: string | null) => void; +} +``` + +**EnchantmentPreparerProps:** +```typescript +export interface EnchantmentPreparerProps { + store: GameStore; + selectedEquipmentInstance: string | null; + setSelectedEquipmentInstance: (id: string | null) => void; +} +``` + +**EnchantmentApplierProps:** +```typescript +export interface EnchantmentApplierProps { + store: GameStore; + selectedEquipmentInstance: string | null; + setSelectedEquipmentInstance: (id: string | null) => void; + selectedDesign: string | null; + setSelectedDesign: (id: string | null) => void; + onEnchantmentApplied?: () => void; + onCapacityExceeded?: (itemName: string, used: number, total: number) => void; +} +``` + +--- + +### 6. Key Observations for Restructuring + +1. **Stepper Removal**: The `CRAFTING_PHASES` constant and `Stepper` component usage must be removed from CraftingTab + +2. **State Management**: The `craftingStage` state will need to be replaced with: + - A top-level tab state (`fabricate` | `enchant`) + - An enchant sub-stage state (`design` | `prepare` | `apply`) when in enchant mode + +3. **Progress Bars**: The activity indicators with Progress components (lines 133-236) should potentially be moved into their respective components (EquipmentCrafter, EnchantmentDesigner, etc.) rather than being in CraftingTab + +4. **No Tab Component**: Currently, the app doesn't use a Tab component - it uses conditional rendering with ActionButtons. The restructured version should implement proper tabs at the top level + +5. **Helper Functions**: The `safeToFixed` and `calcPercent` helpers are used for progress bars - these may no longer be needed in CraftingTab after restructuring diff --git a/docs/task5/subtask_13_context.md b/docs/task5/subtask_13_context.md new file mode 100644 index 0000000..79f55a9 --- /dev/null +++ b/docs/task5/subtask_13_context.md @@ -0,0 +1,282 @@ +# Task 13 Context: Fix LootTab Nesting (Remove Redundant Layers) + +## Task Description +Fix LootTab nesting (remove redundant layers) (PRIORITY 3b) + +## Source Files + +### 1. `/src/components/game/tabs/LootTab.tsx` (48 lines) + +**Full Content:** +```typescript +'use client'; + +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import type { GameStore } from '@/lib/game/store'; +import { LootInventoryDisplay } from '@/components/game/LootInventory'; + +export interface LootTabProps { + store: GameStore; +} + +export function LootTab({ store }: LootTabProps) { + const inventory = store.lootInventory; + const elements = store.elements; + const equipmentInstances = store.equipmentInstances; + + // Count items for badge + const materialCount = Object.values(inventory.materials).reduce((a, b) => a + b, 0); + const blueprintCount = inventory.blueprints.length; + const equipmentCount = Object.keys(equipmentInstances).length; + const totalItems = materialCount + blueprintCount + equipmentCount; + + return ( +
+ + + + πŸ’Ž Loot Inventory + + {totalItems} items + + + + + + + +
+ ); +} + +LootTab.displayName = "LootTab"; +``` + +**Key Observations - LootTab Redundant Wrapper:** +- Uses `Card` component from `@/components/ui/card` with header "πŸ’Ž Loot Inventory" +- Shows a badge with total items count +- Wraps `LootInventoryDisplay` inside `CardContent` +- **This creates the outer layer of nesting** + +--- + +### 2. `/src/components/game/LootInventory.tsx` (499 lines) + +**Component Interface:** +```typescript +interface LootInventoryProps { + inventory: LootInventoryType; + elements?: Record; + equipmentInstances?: Record; + onDeleteMaterial?: (materialId: string, amount: number) => void; + onDeleteEquipment?: (instanceId: string) => void; +} +``` + +**Main Component Export:** +```typescript +export function LootInventoryDisplay({ + inventory, + elements, + equipmentInstances = {}, + onDeleteMaterial, + onDeleteEquipment, +}: LootInventoryProps) { + // ... state and handlers ... + + // Check if we have anything to show + const hasItems = totalItems > 0 || essenceCount > 0; + + if (!hasItems) { + return ( + +
+ +

+ Inventory +

+
+
+ No items collected yet. Defeat floors and guardians to find loot! +
+
+ ); + } + + // ... handlers ... + + return ( + <> + +
+ +

+ Inventory +

+ + {totalItems} items + +
+ + {/* Search and Filter Controls */} + {/* ... */} + + {/* Filter Tabs */} + {/* ... */} + + + + + {/* Materials, Essence, Blueprints, Equipment sections */} + {/* ... */} + +
+ + {/* Delete Confirmation Dialog */} + setDeleteConfirm(null)}> + {/* ... */} + + + ); +} +``` + +**Key Observations - LootInventory Redundant Wrapper:** +- Uses `GameCard` component (from `@/components/ui/game-card`) +- Has its own header with "Inventory" title and `Gem` icon +- Shows a badge with total items count (duplicating LootTab's badge) +- Contains all the actual content: search, filters, items display +- **This creates the inner layer of nesting** + +--- + +## 3. Duplicate Headings/Wrappers Issue + +### Redundant Card Nesting: +``` +LootTab (Outer Card) +β”œβ”€β”€ CardHeader: "πŸ’Ž Loot Inventory" + Badge: "{totalItems} items" +└── CardContent + └── LootInventoryDisplay (Inner GameCard) + β”œβ”€β”€ Header: "Inventory" + Badge: "{totalItems} items" ← DUPLICATE + β”œβ”€β”€ Search/Filter Controls + β”œβ”€β”€ Items Display + └── Delete Dialog +``` + +### Specific Code Duplication: + +**LootTab.tsx (lines 24-33) - Outer Header:** +```tsx + + + πŸ’Ž Loot Inventory + + {totalItems} items + + + +``` + +**LootInventory.tsx (lines 191-202) - Inner Header (DUPLICATE):** +```tsx +
+ +

+ Inventory +

+ + {totalItems} items + +
+``` + +### Redundant Badge Count: +- LootTab shows: `{totalItems} items` +- LootInventoryDisplay also shows: `{totalItems} items` +- Both calculate the same `totalItems` value + +--- + +## 4. Current Component Hierarchy + +``` +Game.tsx / Main Game Layout + β”‚ + └── Tabs Component (renders active tab) + β”‚ + └── LootTab ({ store }) + β”‚ + β”œβ”€β”€ Card (bg-gray-900/80 border-gray-700) + β”‚ β”œβ”€β”€ CardHeader + β”‚ β”‚ └── CardTitle: "πŸ’Ž Loot Inventory" + Badge + β”‚ └── CardContent + β”‚ β”‚ + β”‚ └── LootInventoryDisplay ({ inventory, elements, equipmentInstances, ... }) + β”‚ β”‚ + β”‚ β”œβ”€β”€ GameCard (variant="default" className="w-full") + β”‚ β”‚ β”œβ”€β”€ Header: "Inventory" + Badge + Search/Filter + β”‚ β”‚ β”œβ”€β”€ Separator + β”‚ β”‚ β”œβ”€β”€ ScrollArea + β”‚ β”‚ β”‚ β”œβ”€β”€ Materials Section + β”‚ β”‚ β”‚ β”œβ”€β”€ Essence Section + β”‚ β”‚ β”‚ β”œβ”€β”€ Blueprints Section + β”‚ β”‚ β”‚ └── Equipment Section + β”‚ β”‚ └── (content) + β”‚ β”‚ + β”‚ └── AlertDialog (Delete Confirmation) + β”‚ + └── (end Card) +``` + +--- + +## 5. Comparison with Other Tabs + +Looking at other tabs in `/src/components/game/tabs/`: + +- **EquipmentTab.tsx**: Uses `GameCard` directly without an outer `Card` wrapper +- **CraftingTab.tsx**: Uses multiple `GameCard` components for different sections, no outer `Card` +- **GolemancyTab.tsx**: Uses `GameCard` directly +- **LabTab.tsx**: Uses `GameCard` directly + +**Pattern**: Most tabs render their content directly using `GameCard` components without an additional `Card` wrapper from the tab itself. + +--- + +## 6. Summary of Issues to Fix + +1. **Redundant Card Wrapper in LootTab**: The `Card` + `CardHeader` + `CardContent` wrapper in LootTab.tsx is unnecessary since LootInventoryDisplay already provides its own `GameCard` wrapper. + +2. **Duplicate Header**: Both LootTab and LootInventoryDisplay show similar headers with: + - Title text ("Loot Inventory" vs "Inventory") + - Item count badge + - Icon (emoji πŸ’Ž vs Gem icon) + +3. **Double Wrapping**: Content is wrapped in two card-like components: + - Outer: `Card` from `@/components/ui/card` + - Inner: `GameCard` from `@/components/ui/game-card` + +4. **Solution Direction**: Remove the outer `Card` wrapper from LootTab.tsx and let LootInventoryDisplay handle the card styling, OR remove the inner `GameCard` from LootInventoryDisplay and keep only the outer wrapper. + +--- + +## 7. Files That Would Need Modification + +1. **`/src/components/game/tabs/LootTab.tsx`** - Remove outer Card wrapper, pass props directly to LootInventoryDisplay +2. **`/src/components/game/LootInventory.tsx`** - Potentially remove GameCard wrapper if keeping LootTab's Card, or keep as-is if removing LootTab's Card + +**Recommended Approach**: Remove the outer `Card` wrapper from `LootTab.tsx` and let `LootInventoryDisplay` handle the full display (since it already has a complete GameCard wrapper with header, controls, and content). diff --git a/package-lock.json b/package-lock.json index 6eaa0a6..bc0fdf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,6 +84,7 @@ "bun-types": "^1.3.4", "eslint": "^9", "eslint-config-next": "^16.1.1", + "husky": "^9.1.7", "jsdom": "^29.0.1", "tailwindcss": "^4", "tw-animate-css": "^1.3.5", @@ -9619,6 +9620,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/icu-minify": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.1.tgz", diff --git a/package.json b/package.json index 252d816..9e672ce 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "db:push": "prisma db push", "db:generate": "prisma generate", "db:migrate": "prisma migrate dev", - "db:reset": "prisma migrate reset" + "db:reset": "prisma migrate reset", + "prepare": "husky" }, "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -91,6 +92,7 @@ "bun-types": "^1.3.4", "eslint": "^9", "eslint-config-next": "^16.1.1", + "husky": "^9.1.7", "jsdom": "^29.0.1", "tailwindcss": "^4", "tw-animate-css": "^1.3.5", diff --git a/src/components/game/tabs/LootTab.tsx b/src/components/game/tabs/LootTab.tsx index 953f412..a4824c6 100755 --- a/src/components/game/tabs/LootTab.tsx +++ b/src/components/game/tabs/LootTab.tsx @@ -1,7 +1,5 @@ 'use client'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; import type { GameStore } from '@/lib/game/store'; import { LootInventoryDisplay } from '@/components/game/LootInventory'; @@ -14,34 +12,14 @@ export function LootTab({ store }: LootTabProps) { const elements = store.elements; const equipmentInstances = store.equipmentInstances; - // Count items for badge - const materialCount = Object.values(inventory.materials).reduce((a, b) => a + b, 0); - const blueprintCount = inventory.blueprints.length; - const equipmentCount = Object.keys(equipmentInstances).length; - const totalItems = materialCount + blueprintCount + equipmentCount; - return ( -
- - - - πŸ’Ž Loot Inventory - - {totalItems} items - - - - - - - -
+ ); } diff --git a/test-small.ts b/test-small.ts deleted file mode 100644 index a2248ef..0000000 --- a/test-small.ts +++ /dev/null @@ -1 +0,0 @@ -console.log('test');