Initial extract: templates + methodology guide from Ideation

Public, version-controlled source of truth for TeamIDE's project
templates, coding conventions, and the build-plan → PM scaffolding
rules. Mounted read-only by TeamIDE's planner agent at runtime.

Extracted from the personal Ideation repo as a precondition for
workspace-projects-agent-spec.md §9.4. Personal Ideation stays
private; this repo holds only the publishable methodology.
This commit is contained in:
David Herrera 2026-05-16 10:01:25 -05:00
commit 83c52bc011
95 changed files with 13727 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.DS_Store

27
LICENSE Normal file
View File

@ -0,0 +1,27 @@
MIT Transparency License
Copyright (c) Corey Herrera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software without any modification,
exemption or additional provision. All operational distributions, including
executables, embedded installations, package repositories, container images,
and network deployments must preserve their users' access to original source,
documentation, build scripts, configuration, and modifications necessary to
reproduce the intended implementation, in order to promote operational
transparency for the betterment of humanity.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# TeamIDE Methodology
Project templates, coding conventions, and the build-plan → PM scaffolding rules that [TeamIDE](https://teamide.dev)'s planner agent reads at run time.
This repo is the public, version-controlled source of truth for **how a TeamIDE project gets shaped**:
- `claude.md` — the methodology guide: how to plan a project, the spec/plan/PM-file pipeline, naming conventions, the file structure every project follows, and the docs/InternalDocs split.
- `templates/coding-best-practices.md` — universal coding rules (descriptive names, small functions, library-first design, file-header convention, no auto-save).
- `templates/claude.md` — the standard project CLAUDE.md template (copied and customized into every new project).
- `templates/TauriStarter/` — a working Tauri + Quasar + Vite desktop-app starter.
- `templates/ResearchStarter/` — a hypothesis-first R&D starter.
- `templates/teamide/` — `.teamide/project.yml` and `.teamide/plans/` / `.teamide/tasks/` scaffolding.
- `templates/docs/` — documentation templates (`feature-*.md`, `getting-started.md`, etc.).
## How TeamIDE consumes this
TeamIDE clones this repo to `~/TeamIDE/.data/methodology/` on first launch and refreshes it on demand (manual button in the planner UI, or `git pull` on app start — TBD). The planner agent has **read-only** access to this directory. Writes outside its working dir are forbidden by the prompt.
If you want to customize TeamIDE's planner with your own templates or conventions, fork this repo and point TeamIDE at your fork (configurable in the planner settings).
## Status
Living document — extracted from an earlier private repo on 2026-05-16 to support TeamIDE's planner/conductor agent pipeline (see TeamIDE's `workspace-projects-agent-spec.md` §9.4).
## License
[MIT Transparency License](./LICENSE) — same as TeamIDE itself.

1048
claude.md Normal file

File diff suppressed because it is too large Load Diff

32
templates/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Environment files
.env
.env.*
!.env.example
# Nhost secrets
app/nhost/.secrets
# Node
node_modules/
npm-debug.log*
yarn-error.log*
# Note: Never ignore lock files (package-lock.json, yarn.lock, pnpm-lock.yaml)
# They are required for reproducible builds
# Build output
dist/
.quasar/
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/

27
templates/LICENSE Normal file
View File

@ -0,0 +1,27 @@
MIT Transparency License
Copyright (c) Corey Herrera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software without any modification,
exemption or additional provision. All operational distributions, including
executables, embedded installations, package repositories, container images,
and network deployments must preserve their users' access to original source,
documentation, build scripts, configuration, and modifications necessary to
reproduce the intended implementation, in order to promote operational
transparency for the betterment of humanity.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

73
templates/README.md Normal file
View File

@ -0,0 +1,73 @@
# Project Name
> One-line tagline describing what this project does
![Hero Screenshot](https://raw.githubusercontent.com/dcherrera/ProjectName/main/screenshots/hero.png)
## Overview
Brief description of what the project does and the problem it solves. Keep this to 2-3 paragraphs max.
Explain the motivation - why does this exist? What pain point does it address?
## Features
- **Feature 1** - Brief description
- **Feature 2** - Brief description
- **Feature 3** - Brief description
## Screenshots
![Feature Demo](https://raw.githubusercontent.com/dcherrera/ProjectName/main/screenshots/demo.gif)
![Feature Name](https://raw.githubusercontent.com/dcherrera/ProjectName/main/screenshots/feature-name.png)
## Tech Stack
- **Frontend**: Vue 3, TypeScript, Quasar
- **Backend**: Nhost (PostgreSQL, Hasura, Auth)
- **Desktop**: Tauri (if applicable)
## Getting Started
### Prerequisites
- Node.js 18+
- npm or yarn
### Installation
```bash
git clone https://github.com/dcherrera/ProjectName.git
cd ProjectName/app
npm install
```
### Usage
```bash
npm run dev
```
## Development
```bash
# Run development server
npm run dev
# Build for production
npm run build
# Run tests
npm test
```
## Status
**Active** - Currently in development
<!-- Status options: Active | Maintained | Paused | Archived | Concept -->
## License
[MIT Transparency License](LICENSE)

64
templates/ResearchStarter/.gitignore vendored Normal file
View File

@ -0,0 +1,64 @@
# Data files (large, not committed)
data/
# Environment files
.env
.env.*
!.env.example
# Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
*.egg
dist/
build/
.eggs/
*.whl
venv/
.venv/
# Rust
target/
# Node
node_modules/
npm-debug.log*
yarn-error.log*
# Model weights & checkpoints
*.pt
*.pth
*.ckpt
*.safetensors
*.bin
*.onnx
*.h5
checkpoints/
weights/
# Jupyter
.ipynb_checkpoints/
*.ipynb_metadata/
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Build artifacts
*.o
*.so
*.dylib
*.a
*.out

View File

@ -0,0 +1,387 @@
# Research Project Guidelines
## Project Structure
```
ProjectName/
├── README.md # Project overview (public-facing)
├── LICENSE # MIT Transparency License
├── CLAUDE.md # AI assistant guidelines + project config
├── goals.md # Success criteria (what does "done" look like?)
├── spec.md # Technical specification (optional)
├── conclusions.md # Living findings document
├── open_questions.md # Unresolved questions tracker
├── .gitignore
├── experiments/ # Experiment code (numbered runs)
│ ├── run1-baseline.ext
│ ├── run2-variable-change.ext
│ └── ...
├── results/ # Experiment results documentation
│ ├── TEMPLATE-results.md # Template for new results docs
│ └── experiment_N_results.md # One per experiment group
├── data/ # Datasets (gitignored — large files)
├── literature/ # Paper notes & prior art
│ ├── TEMPLATE-paper.md # Template for paper reviews
│ └── paper-name.md
├── src/ # Implementation code (when research produces software)
├── tests/ # Test suite
├── docs/ # Technical documentation
│ ├── README.md # Docs index
│ ├── TEMPLATE-experiment-plan.md
│ ├── TEMPLATE-milestone.md
│ ├── getting-started.md
│ └── architecture.md
└── screenshots/ # Visuals for README / portfolio
```
## TeamIDE Project Management
If this project was scaffolded from Ideation, `.teamide/` PM files (plans, tasks, project.yml) should already exist — generated from milestone definitions during scaffolding. See the Ideation workspace's `claude.md` for the PM generation workflow and format details.
## Terminology
| Term | Meaning |
|------|---------|
| **goals.md** | What success looks like — pass/fail criteria, anti-goals, kill gates |
| **spec.md** | Technical specification for the system being built or studied (optional) |
| **conclusions.md** | Living findings — updated after each experiment phase |
| **open_questions.md** | Unresolved questions with context and leads |
| **experiment** | A numbered run in `experiments/` with a hypothesis |
| **results doc** | Structured write-up in `results/` documenting what happened |
| **milestone** | A dependency-tracked unit of work with success criteria |
## The Research Pipeline
```
Vision doc --> Math/tradeoff analysis --> Milestone plan --> Implementation --> Tests pass
(speculative) (constrained) (prescriptive) (code) (done)
```
Each stage has a different document type and level of commitment. Ideas flow left to right and can be killed at any stage.
## Document Layers
### Layer 1: Vision Documents
Thinking documents. Speculative, conversational, organized around questions and hypotheses. These live at the repo root or in topic-specific folders.
**Purpose:** Capture the frontier of thinking. These are allowed to be wrong.
**Examples:** Mathematical explorations, system architecture visions, training/data blueprints, goal documents.
### Layer 2: Specs & Plans
Prescriptive. Designed to be turned directly into implementation tasks. Live in `docs/` or as top-level files (`spec.md`).
**Purpose:** Bridge between "what if" and "what to build." Every spec includes tasks, API surface, success criteria, and test cases.
### Layer 3: Research Subprojects
Active investigations with clear experiment plans and validation gates. Each gets its own directory or subfolder within `experiments/` with a README explaining the hypothesis, experiment design, and expected outcomes.
### Layer 4: Architecture Docs
Written after implementation. Describe how things actually work, not how they were planned. Live in `docs/architecture.md`.
**Purpose:** Let a new developer understand the system without reading source code.
## Key Patterns
### 1. Hypothesis First (Non-Negotiable)
**Every experiment requires a written hypothesis before any code runs.** No hypothesis, no experiment.
The hypothesis states what you expect to happen and why — it forces you to think before you compute.
```markdown
## Hypothesis
Increasing batch size from 1 to 32 will reduce gradient noise enough for
convergence, because individual gradient samples have high variance but
batch-averaged gradients should be stable.
## Expected Outcome
Loss should decrease monotonically with batch=32, vs oscillating with batch=1.
If loss still oscillates with batch=32, the variance problem is fundamental,
not just a sample-size issue.
```
Without a hypothesis, you don't know what the experiment is testing. You'll see a number and not know if it's good or bad.
**The scientific method, applied:**
1. **Observe** — notice something in your data, results, or literature
2. **Question** — formulate a specific question about what you observed
3. **Hypothesize** — predict an answer and state WHY you expect it
4. **Experiment** — design a run that can confirm or reject the hypothesis
5. **Analyze** — compare results to the prediction
6. **Conclude** — state what was learned, update your mental model
7. **Repeat** — the conclusion feeds the next observation
### 2. Kill Gates
Research questions get structured investigation with kill gates:
```
Phase 1: Literature review (what exists?)
Phase 2: Build minimal viable module
Phase 3: Integration experiments (defined matrix of configs)
Phase 4: Investigate properties (hypothesis-based, only if Phase 3 passes)
Phase 5: Optimization (only if Phase 4 is promising)
```
Each phase is a gate. If Phase 3 fails, Phase 4 doesn't happen. This prevents sunk-cost fallacy on dead-end research.
### 3. Constraint-Driven Design
Start with hard limits. Let them drive architecture.
```markdown
## Constraints
- [Your hardware/budget/time limit here]
- [Your performance requirement here]
- [Your compatibility requirement here]
## Design (driven by above)
- [Architecture decision that follows from constraints]
```
Don't design the ideal system then optimize. Design around your actual constraints from day one.
### 4. Explicit Deferral
Every milestone says what it's NOT doing and why:
```markdown
**Deferred:** [Feature/capability].
**Reason:** [Why it's not worth doing now].
```
Three categories for all work items:
- **Active/Concrete** — being built now, with tests
- **Outlined** — architectural sketch, no code yet
- **Deferred** — recognized need, explicitly postponed with rationale
This prevents scope creep while preserving ideas for later.
### 5. Tradeoff Tables
Every complex decision gets a table showing the option space:
```markdown
| Option | Pros | Cons | When to pick |
|-----------|------------------|-----------------------|----------------|
| Option A | Simple, fast | Doesn't scale past N | Prototyping |
| Option B | Scales well | Complex to implement | Production |
| Option C | Best performance | Vendor lock-in | If budget > $X |
```
No "we chose X because it's better." Instead: "here's the tradeoff space, here's what we picked and why."
### 6. Testable Success Criteria
Every milestone ends with a concrete, falsifiable statement:
```markdown
**Success Criteria:**
- [System] does [specific thing] in [specific time/resource bound]
- [N] tests pass with zero warnings
- [Specific scenario] produces [specific measurable result]
```
Not "it should work" — specific numbers, specific scenarios.
### 7. Math First, Code Second
Abstract ideas get mathematical or formal framing before any code is written:
```
Idea (what if we tried X?)
--> Formalization (what does X actually mean mathematically?)
--> Existing work (has anyone done this? what libraries exist?)
--> Experiment plan (how would we test this?)
--> Validation gate (what result would make us continue vs abandon?)
```
This filters out ideas that sound good but don't survive formalization.
### 8. Documentation as Specification
Write implementation-ready docs before coding:
1. Write detailed spec in markdown (API signatures, wire formats, data structures)
2. Implement against the spec
3. Test against the spec's success criteria
4. Update the spec if reality diverges
The spec IS the source of truth. Code follows docs, not the other way around.
## Experiment Tracking
All experiments live in the `experiments/` folder and follow strict naming and documentation conventions.
### Naming Convention
Experiments are numbered sequentially with a descriptive kebab-case suffix:
```
experiments/
├── run1-batch-gradient-baseline.c
├── run2-per-sample-online.c
├── run3-leaky-relu-activation.c
├── run4-temperature-annealing.c
└── ...
```
Rules:
- Prefix: `run<N>-` where N is sequential (run1, run2, run3...)
- Suffix: short kebab-case description of what this run changes vs the previous
- One variable changed per run when possible (isolation)
- If a run is a variant of another, use sub-numbering: `run9b-normalization-lower-temp`
- Run numbering stays globally sequential across the whole project — don't restart at run1 inside subfolders
When a single experiment direction spawns many runs, group them in a subfolder inside `experiments/`:
```
experiments/
├── run1-baseline.ext
├── run2-different-approach.ext
├── scaling-study/ # Subfolder for related runs
│ ├── run3-small-scale.ext
│ ├── run4-medium-scale.ext
│ └── run5-large-scale.ext
└── ...
```
### Results Documentation
Every experiment's results are documented in `results/`. One results file per experiment or per logical group:
```
results/
├── experiment_1_results.md
├── experiment_2_results.md
└── ...
```
Use `results/TEMPLATE-results.md` as the starting point for each results file.
### Key Rules for Results
1. **Every run gets a row in the summary table** — even failed runs. Especially failed runs.
2. **Link to source files** — reference experiment files as `experiments/run1-descriptive-name.ext`
3. **Include raw output** — paste actual terminal output. Don't just summarize.
4. **One finding per discovery** — each finding gets its own subsection with a bold **Verdict** line
5. **Comparison tables for pivots** — when switching approaches, add a side-by-side comparison table
6. **Strikethrough completed next steps** — mark done items with `~~strikethrough~~` and add **DONE**
7. **Decision points with kill gates** — define upfront what results would make you continue, pivot, or abandon
8. **Literature context** — compare your results to published work
### Running Experiments
**All experiments MUST be run in the background** so the user can continue chatting while they execute. Never run a build or training run in the foreground — it blocks the conversation. Use background execution and check on results later.
## Milestone Format
Every implementation milestone follows this structure:
```markdown
### Milestone X.Y: Title — [STATUS]
**What:** One-sentence summary of the deliverable.
**Tasks:**
- [ ] Task with enough detail that someone else could do it
- [ ] Another task
**Completed.** [Summary paragraph when done.]
**Deferred:** [What was intentionally left out and why.]
**New Files:**
| File | What |
|------|------|
| path/to/file | One-line description |
**API Surface:**
[Function signatures or interface definitions]
**Depends on:** Milestone X.Y-1
**Success Criteria:** [Falsifiable statement with numbers]
**Test Cases:**
1. [Specific scenario] — [expected result]
2. [Specific scenario] — [expected result]
```
### Dependency Graphs
Milestones form a dependency chain. Visualize it:
```
Phase 1: 1.1 --> 1.2 --> 1.3 --> 1.4 --> 1.5
| |
Phase 2: v v
2.1 --> 2.2 --> 2.3 --> 2.4
```
This makes it obvious what can be parallelized and what blocks what.
## Principles
1. **Hypothesis before experiment** — no code runs without a written prediction of what will happen and why. This is the most important rule.
2. **Speculation is welcome but quarantined** — vision docs are clearly marked as exploratory
3. **Implementation follows specs** — not the reverse
4. **Deferral is first-class** — saying "not now" with a reason is as important as saying "yes"
5. **Constraints are features** — limits drive creative solutions rather than being obstacles
6. **Tests are the definition of done** — not "it compiles" but "N tests pass with zero warnings"
7. **Tradeoffs are documented** — future-you can understand why decisions were made
8. **Kill gates protect your time** — research that fails a gate gets abandoned, not propped up
9. **Every experiment is tracked** — numbered, named, with raw output in results files. Failed runs are documented just as thoroughly as successes.
10. **One variable per run** — isolate changes so you know what caused the result. Sub-number variants (run9b) when testing minor tweaks.
11. **Experiments run in the background** — never block the user's ability to chat. All builds, training runs, and long-running processes execute in the background.
## Commit Rules
- Never mention Claude, AI, or generated code in commits
- Write as the developer, first person
- Concise and descriptive
## Customization Checklist
When creating a new project from this template:
- [ ] Update `README.md` with project name, problem, idea, approaches
- [ ] Fill in `goals.md` with success criteria from the project plan
- [ ] Fill in `spec.md` if the project has a technical specification (otherwise delete it)
- [ ] Set up `open_questions.md` with initial research questions
- [ ] Create initial experiment plans in `docs/` using `TEMPLATE-experiment-plan.md`
- [ ] Add project-specific entries to `.gitignore` (language-specific, data formats, etc.)
- [ ] Update the "Project-Specific Configuration" section below
- [ ] Initialize git and create GitHub repo
---
# Project-Specific Configuration
<!-- Everything below this line is customized per project -->
## Project Name
[PROJECT_NAME]
## Description
[Brief description of the research project]
## Research Question
[The core question this project investigates]
## Tech Stack
[Languages, frameworks, libraries being used]
## TeamIDE Workspace ID
[Workspace ID if deployed via TeamIDE]

View File

@ -0,0 +1,27 @@
MIT Transparency License
Copyright (c) Corey Herrera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software without any modification,
exemption or additional provision. All operational distributions, including
executables, embedded installations, package repositories, container images,
and network deployments must preserve their users' access to original source,
documentation, build scripts, configuration, and modifications necessary to
reproduce the intended implementation, in order to promote operational
transparency for the betterment of humanity.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,67 @@
# [Project Name]
> One-line description of what this research investigates.
## The Problem
[What problem exists. Why current solutions are insufficient. 2-3 paragraphs max.]
## The Idea
[Your proposed approach. What makes it different. Include a diagram or table if helpful.]
## Current Status
[What works, what doesn't, what's next. Update this as the project progresses.]
| Component | Status | Key Metric |
|-----------|--------|------------|
| ... | ... | ... |
## Research Approaches
[List each approach being investigated with pros/cons.]
### Approach 1: [Name]
- **Pro:** ...
- **Con:** ...
- **Open question:** ...
## Experiment Plan
[Numbered list of planned experiments with goals.]
### Experiment 1: [Name]
**Goal:** [One sentence.]
- [Details]
- [Key question this answers]
## Success Criteria
[What results would make this research succeed vs fail.]
1. [Criterion with specific number]
2. [Criterion with specific number]
If no approach meets these criteria: [fallback plan].
## Folder Structure
```
ProjectName/
├── experiments/ # Numbered experiment runs
├── results/ # Experiment results documentation
├── data/ # Datasets (gitignored)
├── literature/ # Paper notes & prior art
├── src/ # Implementation code
├── tests/ # Test suite
└── docs/ # Technical documentation
```
## References
- [Paper/resource](URL) — one-line note
## License
[MIT Transparency License](LICENSE)

View File

@ -0,0 +1,27 @@
# Conclusions: [Project Name]
## Status: In Progress
## Key Findings
### Finding 1: [Title]
**Discovered in:** [Experiment N, Phase X]
**Summary:** [One paragraph]
**Evidence:** See `results/experiment_N_results.md`
**Impact:** [How this changes our approach]
## What Worked
1. [Approach/technique that succeeded and why]
## What Didn't Work
1. [Approach/technique that failed and why — this is equally valuable]
## Final Recommendation
[What to do with these findings. Ship it? Pivot? Integrate into another project?]
## Open Questions Remaining
1. [Question that this research didn't answer]

View File

@ -0,0 +1,42 @@
# Documentation
> Technical documentation for [Project Name]
## Table of Contents
### Getting Started
- [Setup Guide](./getting-started.md)
### Research
<!-- Add experiment plans and milestone docs as the project progresses -->
<!-- Example: [Experiment 1 Plan](./experiment-1-plan.md) -->
### Architecture
- [Architecture](./architecture.md) — Post-implementation technical design
### Templates
- [Experiment Plan Template](./TEMPLATE-experiment-plan.md) — For planning new experiments
- [Milestone Template](./TEMPLATE-milestone.md) — For milestone-based planning
---
## Documentation Guidelines
### Adding New Documentation
After completing an experiment phase or milestone:
1. Create a new `.md` file in this folder
2. Add a link to the Table of Contents above
3. Use the appropriate template:
- [Experiment plan template](./TEMPLATE-experiment-plan.md) — For planning experiments
- [Milestone template](./TEMPLATE-milestone.md) — For implementation milestones
### Naming Convention
| Type | Pattern | Example |
|------|---------|---------|
| Experiment plans | `experiment-N-plan.md` | `experiment-1-plan.md` |
| Milestone docs | `milestone-N-name.md` | `milestone-1-foundation.md` |
| Topic research | `research-[topic].md` | `research-gradient-methods.md` |
| Guide docs | `guide-[topic].md` | `guide-data-preparation.md` |

View File

@ -0,0 +1,21 @@
# Run N: [Descriptive Name]
## Hypothesis
[What we expect to happen and why.]
## Setup
- **Architecture:** [Model/system description]
- **Key change vs previous run:** [What's different]
- **Config:** [Hyperparameters, settings]
## Expected Outcome
[What result would confirm or reject the hypothesis.]
## Run Command
```bash
[How to build and run this experiment]
```

View File

@ -0,0 +1,66 @@
# Milestone Plan: [Project Name]
## Dependency Graph
```
Phase 1: 1.1 --> 1.2 --> 1.3
|
Phase 2: v
2.1 --> 2.2 --> 2.3
```
---
## Phase 1: [Phase Name]
### Milestone 1.1: [Title] — PENDING
**What:** [One-sentence summary of the deliverable.]
**Tasks:**
- [ ] Task with enough detail that someone else could do it
- [ ] Another task
**Deferred:** [What was intentionally left out and why.]
**New Files:**
| File | What |
|------|------|
| path/to/file | One-line description |
**API Surface:**
[Function signatures or interface definitions, if applicable]
**Depends on:** None
**Success Criteria:** [Falsifiable statement with numbers]
**Test Cases:**
1. [Specific scenario] — [expected result]
2. [Specific scenario] — [expected result]
### Milestone 1.2: [Title] — PENDING
**What:** ...
**Depends on:** Milestone 1.1
...
---
## Phase 2: [Phase Name]
### Milestone 2.1: [Title] — PENDING
**Depends on:** Milestone 1.2
...
---
## Kill Gates
- **After Phase 1:** If [condition], abandon [approach] and [fallback action]
- **After Phase 2:** If [condition], pivot to [alternative]

View File

@ -0,0 +1,35 @@
# Architecture
> Technical architecture for [Project Name]
**Note:** This document is written after implementation. It describes how things actually work, not how they were planned. For the original design, see [spec.md](../spec.md).
## Tech Stack
| Layer | Technology | Purpose |
|-------|------------|---------|
| [Layer] | [Technology] | [Purpose] |
## System Design
[Diagrams, data flow, component interactions.]
## Key Design Decisions
### [Decision 1]
**Context:** [Why this decision was needed]
**Decision:** [What was decided]
**Consequences:** [Trade-offs and implications]
## Data Model
[Describe main entities, formats, and relationships]
## External Dependencies
| Dependency | Purpose | Documentation |
|------------|---------|---------------|
| [Library] | [Purpose] | [Link] |

View File

@ -0,0 +1,47 @@
# Getting Started
> Setup guide for [Project Name]
## Prerequisites
- [Language runtime / compiler]
- [Package manager]
- [Additional prerequisites]
## Installation
```bash
# Clone the repository
git clone https://github.com/dcherrera/[ProjectName].git
cd [ProjectName]
# Install dependencies
[install command]
```
## Running Experiments
```bash
# Run a specific experiment
[command to run experiment]
# View results
[command to view output]
```
## Project Layout
| Folder | Purpose |
|--------|---------|
| `experiments/` | Experiment code (numbered runs) |
| `results/` | Experiment results documentation |
| `data/` | Datasets (gitignored) |
| `literature/` | Paper notes & prior art |
| `src/` | Implementation code |
| `tests/` | Test suite |
## Next Steps
- Read [goals.md](../goals.md) to understand success criteria
- Check [open_questions.md](../open_questions.md) for current research questions
- Review [Architecture](./architecture.md) for technical design

View File

@ -0,0 +1,36 @@
# Goals: [Project Name]
## Primary Goal
[One sentence: what does this research need to achieve to be worth the time?]
## Success Criteria
These are pass/fail — either you hit them or you don't.
1. [Specific measurable criterion]
2. [Specific measurable criterion]
3. [Specific measurable criterion]
## Stretch Goals
Nice to have, not required for success.
1. [Stretch criterion]
## Anti-Goals
What this project is NOT trying to do. Prevents scope creep.
1. [Explicit non-goal]
2. [Explicit non-goal]
## Fallback Plan
If no approach meets the success criteria:
- [What you'll do instead]
## Timeline
- **Phase 1 deadline:** [Date or "N weeks from start"]
- **Kill gate:** If Phase 1 doesn't pass by [deadline], [action]

View File

@ -0,0 +1,11 @@
# [Paper Title] ([Author Year])
**Link:** [URL]
**Key idea:** [One sentence]
**Relevance:** [How it relates to our research]
**What they got:** [Their results]
**What we can use:** [Actionable takeaway]

View File

@ -0,0 +1,17 @@
# Open Questions: [Project Name]
## Active Questions
### Q1: [Question]
**Context:** [Why this matters]
**Blocking:** [What can't proceed until this is answered, if anything]
**Leads:** [Any partial answers or places to look]
## Answered Questions
<!-- Move questions here as they're resolved -->
<!--
### ~~Q0: [Question]~~
**Answer:** [What we found]
**Discovered in:** [Experiment N / Phase X / literature review]
-->

View File

@ -0,0 +1,81 @@
# Experiment N Results: [Short Title]
## Phase [A/B/C...] — [Phase Name]
### Setup
- **Architecture:** [Model/system description]
- **Source:** `experiments/run1-descriptive-name.ext`
- **Config:** [Key hyperparameters or settings]
---
## Summary of All Runs
| Run | Key Variable | Config | Best Metric | Key Finding |
|-----|-------------|--------|-------------|-------------|
| 1 | baseline | ... | ... | [One-line takeaway] |
| 2 | changed X | ... | ... | [One-line takeaway] |
| 3 | changed Y | ... | ... | [One-line takeaway] |
---
## Detailed Findings
### Finding 1: [Descriptive Title]
**Runs [N-M]** ([what was varied])
[Explanation of what happened and why. Include raw output.]
```
[Raw output from the run — config, per-epoch metrics, final results]
```
**Verdict:** [One-line conclusion. Bold the action item.]
### Finding 2: [Next Discovery]
...
---
## Current Best Result
**Run N: [Short description of the winning config]**
```
[Full output of the best run]
```
---
## Conclusions
[What the phase proved or disproved. Structural insights, not just numbers.]
1. **[Finding]:** [Explanation]
2. **[Finding]:** [Explanation]
3. **[Finding]:** [Explanation]
---
## Next Steps
Priority order:
1. ~~**Run N: [Description]**~~ — **DONE.** [Result summary.]
2. **Run N+1: [Description]** — *Next up.* [Why this is the next thing to try.]
3. **Run N+2: [Description]** — [Brief rationale.]
**Decision points:**
- **>[Target] achieved:** Proceed to [next phase]
- **Stuck at [range]:** [What that means and what to try]
- **Below [floor] after remaining runs:** [Kill gate — abandon or pivot]
### Literature Context
| Method | Result | Notes |
|--------|--------|-------|
| [Existing work] | [Their result] | [How it compares] |
| **Us (Run N)** | **[Our result]** | **[Where we stand]** |

View File

@ -0,0 +1,37 @@
# Spec: [System/Component Name]
## Overview
[What this system does. One paragraph.]
## Constraints
- [Hard limit 1]
- [Hard limit 2]
- [Hard limit 3]
## Architecture
[System design. Diagrams, data flow, component interactions.]
## API Surface
[Function signatures, data formats, wire protocols — whatever the system exposes.]
```
// Example
void system_do_thing(input_t *in, output_t *out);
```
## Data Formats
[File formats, serialization, storage layout.]
## Integration Points
[How this system connects to other projects or systems.]
## Open Design Questions
- [Question that needs resolution before implementation]
- [Question]

View File

View File

36
templates/TauriStarter/.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# Environment files
.env
.env.*
!.env.example
# Nhost secrets
app/nhost/.secrets
# Node
node_modules/
npm-debug.log*
yarn-error.log*
# Note: Never ignore lock files (package-lock.json, yarn.lock, pnpm-lock.yaml)
# They are required for reproducible builds
# Build output
dist/
.quasar/
build/
# Tauri / Rust
app/src-tauri/target/
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/

View File

@ -0,0 +1,537 @@
# Project Guidelines
## Protecting This File
**IMPORTANT**: This file (CLAUDE.md) can be lost during branch operations!
Git doesn't protect untracked/ignored files during `git checkout` or branch switches. Before major git operations:
```bash
cp CLAUDE.md CLAUDE.md.backup
```
## Project Structure
This project follows a standardized structure for Tauri desktop apps.
```
ProjectRoot/
├── CLAUDE.md # AI assistant guidelines
├── README.md # Project documentation
├── build_spec.md # Full project specification
├── build_plan.md # Phased task checklist
├── .gitignore
├── docs/ # Technical documentation
│ ├── README.md # Docs index with table of contents
│ ├── getting-started.md
│ ├── architecture.md
│ ├── development.md
│ ├── deployment.md
│ └── feature-*.md # Feature docs (added per phase)
└── app/ # ALL project code lives here
├── build.sh # Build script
├── package.json
├── vite.config.ts
├── tsconfig.json
├── index.html
├── src/
│ ├── main.ts
│ ├── App.vue
│ ├── boot/
│ │ └── console-logger.ts
│ ├── components/
│ ├── composables/
│ ├── css/
│ ├── layouts/
│ ├── pages/
│ ├── router/
│ ├── stores/
│ ├── services/
│ └── types/
└── src-tauri/
├── Cargo.toml
├── tauri.conf.json
├── icons/
└── src/
└── main.rs
```
## TeamIDE Project Management
If this project was scaffolded from Ideation, `.teamide/` PM files (plans, tasks, project.yml) should already exist — generated from `build_plan.md` during scaffolding. See the Ideation workspace's `claude.md` for the PM generation workflow and format details.
## Terminology
- **"build spec"** or **"spec"** refers to `build_spec.md` - the full project description
- **"build plan"** or **"plan"** refers to `build_plan.md` - the phased task checklist
## Onboarding Questions (Ask at Project Start)
At the start of every new project conversation, gather these details before writing any code:
1. **Brand colors & fonts** - Primary, secondary, accent colors. Typography preferences. Any existing brand guidelines or color palette?
2. **App personality & tone** - Formal or casual? Dark or light default theme? Dense or spacious layout? Any design inspiration or reference apps?
3. **Target platform** - Desktop-first, mobile-first, or responsive? Specific OS targets (macOS, Windows, Linux)? Minimum screen sizes?
4. **Auth requirements** - Login method (email/password, OAuth, magic link)? User roles and permissions? Public or private app?
Don't assume defaults for any of these. Ask, get answers, then document them in the Project-Specific Configuration section at the bottom of this file.
## Tech Stack
- **Frontend**: Quasar Framework (Vue 3) + Vite
- **Desktop**: Tauri (Rust) - Cross-platform builds (macOS, Windows, Linux)
- **State**: Pinia
- **Routing**: Vue Router
- **Backend** (optional): Nhost (self-hosted or cloud)
- PostgreSQL - Database
- Hasura - GraphQL API
- Auth - Authentication service
- Storage - File storage
- Functions - Serverless functions (Node.js)
## Testing Changes (CRITICAL)
**After every code editing session, you MUST rebuild the app so the user can test your changes.**
```bash
cd app
./build.sh -dev -open
```
### Why This Matters
- This uses a **build-and-run** approach, not hot reload
- Your code changes are NOT visible until you rebuild
- Always rebuild before asking the user to test
- If the user reports issues, check the logs first: `./build.sh logs`
## Build Script
**Always use `build.sh` for building the app.** This ensures consistent builds and logging for collaborative debugging.
```bash
cd app
./build.sh -dev -open # Build dev and open app
./build.sh -prod # Build prod to build/prod/
./build.sh -install # Build prod and open Finder for drag-to-Applications
./build.sh logs # View latest console output (last 100 lines)
./build.sh clean # Clean build artifacts (preserves logs)
./build.sh clean-logs # Remove all logs
```
### Commands
| Command | Description |
|---------|-------------|
| `-dev` | Development build. Bundle ID: `com.[app].dev` |
| `-prod` | Production build. Builds to `build/prod/`. |
| `-install` | Build prod and open Finder for drag-to-Applications. |
| `-open` | Open the app after building (use with `-dev` or `-prod`) |
| `-secure` | Build without automation (no HTTP server on port 9876) |
| `logs` | Show last 100 lines of console output |
| `clean` | Remove build artifacts (preserves logs) |
| `clean-logs` | Remove all log files |
### Why Build-and-Run (No Hot Reload)
This project uses a **build-and-run** approach instead of hot reload:
| Aspect | Hot Reload | Build-and-Run |
|--------|------------|---------------|
| Speed | Instant updates | Full rebuild required |
| Dev/Prod parity | Different environments | Identical to production |
| Debugging | Dev-only issues possible | What you test = what ships |
| Logging | Console only | Full logs to files |
The tradeoff is slower iteration, but you always test exactly what will ship.
### What the Build Does
1. **Build with Tauri** - `npx tauri build` does all of the following:
- Build frontend (`npm run build` → `dist/`)
- Build Rust binary with embedded frontend
- Create .app bundle in `src-tauri/target/release/bundle/macos/`
2. **Copy to build folder** - Copies bundle to `build/dev/` or `build/prod/`
3. **Update Info.plist** - Sets correct bundle ID for dev/prod
4. **Install (prod only)** - Copies to `~/Applications`
**Important**: Using `npx tauri build` ensures the frontend is properly embedded in the binary. Manual `cargo build --release` does NOT embed the frontend.
### Build Output
Builds are organized in separate folders:
```
app/build/
├── dev/
│ └── AppName.app # Dev build (bundle ID: .dev)
├── prod/
│ └── AppName.app # Prod build
├── logs/
│ ├── build_dev_*.log # Build output logs
│ └── build_latest.log # Symlink to latest
└── console/
├── console_*.log # App runtime console output
└── console_latest.log # Symlink to latest
```
Dev and prod have different bundle IDs, so both can run simultaneously.
### Logging
All build output and app console output is logged:
- **Build logs**: `build/logs/build_latest.log` - compilation output, errors
- **Console logs**: `build/console/console_latest.log` - runtime `console.log()`, errors
When debugging, run `./build.sh logs` to see the latest console output.
### Console Logging (Already Configured)
This template includes console logging out of the box. Frontend `console.log()` calls are forwarded to Rust and captured in log files.
**How it works:**
```
console.log() → console-logger.ts → invoke('log_to_stdout') → Rust println!() → stdout → build.sh logs
```
## Automation Plugin (Already Configured)
This template includes an automation HTTP server for external testing and AI-assisted development.
**How it works:**
```
External Tool (curl) → HTTP Server (port 9876) → Rust → JavaScript → DOM
```
### Automation API
When the app is running, these endpoints are available:
```bash
# Health check
curl http://localhost:9876/automation/health
# Execute a command
curl -X POST http://localhost:9876/automation/execute \
-H "Content-Type: application/json" \
-d '{"command": "click", "args": {"selector": "#my-button"}}'
# Take a screenshot
curl http://localhost:9876/automation/screenshot --output screenshot.png
```
### Available Commands
| Command | Args | Description |
|---------|------|-------------|
| `navigate` | `{path}` | Navigate to route |
| `click` | `{selector}` | Click element |
| `type` | `{selector, text}` | Type into input |
| `getText` | `{selector}` | Get element text |
| `getValue` | `{selector}` | Get input value |
| `exists` | `{selector}` | Check if element exists |
| `waitFor` | `{selector, timeout?}` | Wait for element |
| `getUrl` | `{}` | Get current URL |
| `screenshot` | - | Capture page (via endpoint) |
Full command list in `src/automation/README.md`.
### Disabling Automation
**Option 1: Build flag (recommended)**
Use the `-secure` flag to build without automation:
```bash
./build.sh -prod -secure # Production build without automation
```
**Option 2: Permanently disable**
Remove `automation` from the default features in `Cargo.toml`:
```toml
[features]
default = ["custom-protocol"] # Remove "automation"
```
### When to Clean
Run `./build.sh clean` when:
- Switching between major dependency versions
- Build errors reference stale/cached files
- Rust compilation issues after Tauri config changes
- Before creating a fresh production build
## Node.js Environment
**Node.js version**: Always match Debian stable (currently Node.js 18.x for Debian 12 Bookworm)
Check current Debian stable Node version: https://packages.debian.org/stable/nodejs
**Package manager**: Use npm. Commit lock files for reproducible builds.
Rules:
- Always commit `package-lock.json` - required for deterministic installs
- Never ignore lock files (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`)
- Use `npm install` to install dependencies
- Use `npm run <script>` to run package scripts
## Communication Rules
**Always explain before acting.** Before running any command, SSH, database query, or making changes:
- Give a short, concise explanation of WHAT you're about to do and WHY
- Wait for approval on anything that modifies production data or server state
- Don't chain multiple server commands without explaining each step
## Database Rules (If Using Nhost Backend)
**NEVER make direct database changes via Hasura MCP or Hasura Console.**
All database changes MUST be done through migrations so they:
1. Are tracked in version control
2. Persist across restarts (`nhost down` / `nhost up`)
3. Can be reproduced in production
### Hasura MCP Usage
- **READ-ONLY**: Use Hasura MCP only to inspect schema, check what tables exist, verify tracking status
- **NO MUTATIONS**: Never use it to create tables, add columns, track tables, or modify permissions
- **MIGRATIONS ONLY**: All schema changes go in `app/nhost/migrations/default/`
- **METADATA ONLY**: All Hasura config (tracking, permissions, relationships) go in `app/nhost/metadata/`
### How to Make Database Changes
1. Write SQL migration in `app/nhost/migrations/default/<N>_<name>/up.sql` (N = next version number)
2. Write rollback in `app/nhost/migrations/default/<N>_<name>/down.sql`
3. Add Hasura metadata YAML in `app/nhost/metadata/databases/default/tables/`
4. Restart dev environment to apply migrations
5. Commit and push
### Hasura Role Permissions (CRITICAL)
**NEVER define permissions for the `admin` role in Hasura metadata.**
Hasura reserves `admin` as a special role with **full unrestricted access**. Defining explicit permissions for `admin` causes metadata errors.
Define permissions only for `user`, `public`, and custom roles.
## Development Approach
**Integrate-as-you-go** - Connect real backend services as you build. Don't build elaborate mock systems.
## Persistence Rules (CRITICAL)
**All persistence requires an explicit user action. No exceptions.**
Never auto-save. Never sync state in the background. Never write to the database on a timer or watcher. Every single write to a database, file, or storage must be traceable to a deliberate user action.
### The Rule
Data is only persisted when the user deliberately triggers it — clicking "Save", submitting a form, confirming a dialog, toggling a setting, etc.
### WRONG — Never Do This
- Auto-saving form fields as the user types
- Syncing component state to the database on mount/unmount
- Watchers that write to storage when reactive data changes
- Debounced save on input change
- Persisting UI state automatically (sidebar position, tab selection, scroll position)
- Writing to the database during navigation or route changes
- Background sync intervals or polling that writes data
### RIGHT — Always Do This
- User clicks "Save" → write to database
- User submits a form → persist the data
- User explicitly toggles a setting → save that setting
- User clicks "Delete" → confirm, then remove from database
- User drags an item to reorder → save on drop (explicit action)
### Why This Matters
Auto-save creates unpredictable behavior, race conditions, and persists data the user didn't intend to save. Every write should be traceable to a conscious user decision. If you can't point to the button or action the user took to trigger the save, it shouldn't be saving.
### Implementation Pattern
```
User Action → Service Call → Database Write → Confirm Success to User
```
1. User triggers action (click, submit, confirm)
2. Call the appropriate service/API
3. Write to database
4. Show success feedback (Quasar Notify or UI update)
5. If it fails, show an error — don't silently retry
## Code Quality
### TypeScript
- Use TypeScript strict mode
- Define interfaces/types for all data structures in `src/types/`
- No `any` types unless absolutely unavoidable — use `unknown` and narrow instead
- Type all function parameters and return values
### Vue / Quasar Conventions
- Use Composition API with `<script setup lang="ts">` for all components
- One component per file, named in PascalCase (e.g., `UserProfile.vue`)
- Composables go in `src/composables/`, prefixed with `use` (e.g., `useAuth.ts`)
- Keep components focused — if it's doing too much, split it
- Props down, events up — don't mutate props, emit events to parent
### Naming
- **Files**: PascalCase for components (`UserCard.vue`), camelCase for everything else (`userService.ts`)
- **Variables/functions**: camelCase
- **Constants**: UPPER_SNAKE_CASE for true constants, camelCase for derived/computed values
- **Types/Interfaces**: PascalCase, no `I` prefix (use `User` not `IUser`)
- **CSS classes**: kebab-case
### General
- No magic numbers or strings — use named constants
- Keep functions short and single-purpose
- Colocate related code — don't scatter logic across files unnecessarily
- Delete dead code, don't comment it out
## Error Handling
### User-Facing Errors
- Always show human-readable messages — never expose raw error objects, stack traces, or technical details to the user
- Use Quasar's `Notify` plugin for transient feedback (success, warning, error)
- Use inline error messages for form validation
### Technical Errors
- Log technical details to console for debugging (`console.error` with context)
- Include the operation that failed and relevant IDs in log messages
- Never swallow errors silently — if you catch it, either handle it or log it
### Pattern
```typescript
try {
const result = await userService.updateProfile(data)
Notify.create({ type: 'positive', message: 'Profile updated' })
} catch (error) {
console.error('Failed to update profile:', error)
Notify.create({ type: 'negative', message: 'Could not update profile. Try again.' })
}
```
### Where to Catch
- Catch at **service boundaries** (API calls, database operations), not deep in utility functions
- Let errors propagate up to where they can be meaningfully handled
- Don't wrap every line in try/catch — catch at the level where you can show the user feedback
## Documentation
**Every phase ends with documentation.** After completing a feature or phase:
1. **Create feature docs** - Copy `docs/TEMPLATE-feature.md` to `docs/feature-[name].md`
2. **Update the index** - Add links to `docs/README.md` table of contents
3. **Update core docs** - Keep `architecture.md`, `development.md` current
### Docs Structure
| File | Purpose |
|------|---------|
| `docs/README.md` | Index with table of contents |
| `docs/getting-started.md` | Installation & setup |
| `docs/architecture.md` | Tech stack & design decisions |
| `docs/development.md` | Development workflow |
| `docs/deployment.md` | Build & deployment |
| `docs/feature-*.md` | Feature-specific documentation |
### Naming Convention
- `feature-[name].md` - Feature docs (e.g., `feature-authentication.md`)
- `component-[name].md` - Component docs
- `integration-[name].md` - Integration docs
- `guide-[topic].md` - How-to guides
## Commit Rules
- **Never mention Claude, AI, or "Generated with Claude Code" in commits**
- All commits are authored by the developer - no co-author tags
- Write commit messages in first person as the developer
- Keep commit messages concise and descriptive of the changes
## Branch Strategy
- **main** - Production branch
- **test** - Staging/testing branch
**Workflow:**
1. Develop and test locally
2. Push to `test` branch for staging
3. Verify on staging
4. Merge `test` → `main` for production
## Customization Checklist
When using this template for a new project:
1. Update `app/src-tauri/tauri.conf.json`:
- `productName`
- `identifier` (e.g., `com.yourapp.app`)
- `title`
2. Update `app/package.json`:
- `name`
- `description`
3. Update `app/build.sh`:
- `APP_NAME`
- `BINARY_NAME` (must match productName)
- `BUNDLE_ID_DEV`
- `BUNDLE_ID_PROD`
4. Replace placeholder icons in `app/src-tauri/icons/`
5. Update documentation:
- Replace `[Project Name]` in all `docs/*.md` files
- Update `docs/architecture.md` with project-specific tech decisions
- Update `docs/getting-started.md` with project-specific setup
6. Add your pages, components, and stores
7. Add Tauri commands to `src-tauri/src/main.rs`
---
# Project-Specific Configuration
<!-- Everything below this line is customized per project -->
## Project Name
[PROJECT_NAME]
## Description
[Brief description from build_spec.md]
## Backend (If Applicable)
| Service | URL |
|---------|-----|
| GraphQL | |
| Auth | |
| Storage | |
## Database Schema (If Applicable)
[Tables and relationships]
## TeamIDE Hosting (If Applicable)
- **Workspace ID**: `your-workspace-id-here`
- Use `teamide_deploy` to deploy changes
- Use `teamide_set_env_vars` to configure environment variables
- Use `teamide_get_logs` to check build status

View File

@ -0,0 +1,77 @@
# TauriStarter
> Starter template for building desktop apps with Quasar (Vue 3), Vite, and Tauri
## Overview
TauriStarter is a ready-to-use template for cross-platform desktop applications. Combines the power of Vue 3 with Quasar's component library and Tauri's lightweight native runtime. Includes a build script for consistent dev/prod builds with logging.
## Features
- **Quasar Framework** - Vue 3 with Quasar components and utilities
- **Tauri Runtime** - Lightweight Rust-based native app shell
- **Build Script** - Consistent builds with dev/prod separation
- **Console Logging** - Frontend logs captured to files for debugging
- **Hot-Swappable Structure** - Standardized layout for component reuse
## Tech Stack
- **Frontend**: Quasar Framework (Vue 3) + Vite
- **Desktop**: Tauri (Rust)
- **State**: Pinia
- **Routing**: Vue Router
## Getting Started
### Prerequisites
- Node.js 18+
- Rust (for Tauri)
- Xcode Command Line Tools (macOS)
### Development
```bash
cd app
npm install
# Web dev server
npm run dev
# Native app dev
npm run tauri:dev
```
### Building
```bash
cd app
# Dev build - opens app
./build.sh -dev -open
# Prod build - installs to ~/Applications
./build.sh -prod
# View console logs
./build.sh logs
# Clean build artifacts
./build.sh clean
```
## Customization
When using this template for a new project, update:
1. `app/src-tauri/tauri.conf.json` - productName, identifier, title
2. `app/package.json` - name, description
3. `app/build.sh` - APP_NAME, BUNDLE_ID_DEV, BUNDLE_ID_PROD
## Screenshots
<!-- Add screenshots to the screenshots/ folder -->
## Status
**Template** - Ready for use

View File

@ -0,0 +1,348 @@
#!/bin/bash
#
# Desktop Build Script Template (Tauri)
# Builds the Tauri desktop app for macOS
#
# Usage:
# ./build.sh -dev # Development build (runs from build folder)
# ./build.sh -dev -open # Development build and open app
# ./build.sh -prod # Production build (builds to build/prod/)
# ./build.sh -install # Build and open Finder for drag-to-Applications
# ./build.sh clean # Clean build folder (preserves logs)
# ./build.sh clean-logs # Clean all logs
# ./build.sh logs # View latest console log
#
# ============================================================
# PROJECT CONFIGURATION - Customize these for your project
# ============================================================
APP_NAME="TauriStarter" # Display name of the app
BUNDLE_ID_BASE="com.tauristarter.app" # Base bundle ID
VERSION="0.1.0" # App version
BINARY_NAME="TauriStarter" # Rust binary name (matches productName in tauri.conf.json)
# ============================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "${BLUE}[Build]${NC} $1"; }
success() { echo -e "${GREEN}[Build]${NC} $1"; }
warn() { echo -e "${YELLOW}[Build]${NC} $1"; }
error() { echo -e "${RED}[Build]${NC} $1"; exit 1; }
# Paths
TAURI_DIR="$SCRIPT_DIR/src-tauri"
# Build folder structure
BUILD_DIR="$SCRIPT_DIR/build"
DEV_BUILD_DIR="$BUILD_DIR/dev"
PROD_BUILD_DIR="$BUILD_DIR/prod"
LOGS_DIR="$BUILD_DIR/logs"
CONSOLE_DIR="$BUILD_DIR/console"
PROD_INSTALL_DIR="$HOME/Applications"
# Timestamp for log files
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
# Parse arguments
BUILD_MODE=""
OPEN_APP=false
SHOW_INSTALL=false
CLEAN_BUILD=false
CLEAN_LOGS=false
SHOW_LOGS=false
SECURE_BUILD=false
while [[ $# -gt 0 ]]; do
case $1 in
-prod|--prod|--production)
BUILD_MODE="prod"
shift
;;
-dev|--dev|--development)
BUILD_MODE="dev"
shift
;;
-open|--open)
OPEN_APP=true
shift
;;
-install|--install)
SHOW_INSTALL=true
BUILD_MODE="prod"
shift
;;
-secure|--secure)
SECURE_BUILD=true
shift
;;
clean|--clean)
CLEAN_BUILD=true
shift
;;
clean-logs|--clean-logs)
CLEAN_LOGS=true
shift
;;
logs|--logs)
SHOW_LOGS=true
shift
;;
-h|--help)
echo "$APP_NAME Build Script"
echo ""
echo "Usage: $0 [-dev|-prod] [-open] [-secure] [clean|clean-logs|logs]"
echo ""
echo "Build Commands:"
echo " -dev, --dev Development build (runs from build folder)"
echo " -prod, --prod Production build (builds to build/prod/)"
echo " -install Build prod and open Finder for drag-to-Applications"
echo " -open, --open Open the app after building"
echo " -secure Build without automation (no HTTP server on port 9876)"
echo ""
echo "Other Commands:"
echo " clean Clean build artifacts (preserves logs)"
echo " clean-logs Remove all logs"
echo " logs Show latest console log (last 100 lines)"
echo " -h, --help Show this help"
echo ""
echo "Build folders:"
echo " Dev: $DEV_BUILD_DIR"
echo " Prod: $PROD_BUILD_DIR (also installs to $PROD_INSTALL_DIR)"
echo " Logs: $LOGS_DIR"
echo " Console: $CONSOLE_DIR"
echo ""
echo "Examples:"
echo " ./build.sh -dev -open # Build dev and open app"
echo " ./build.sh -prod # Build prod to build/prod/"
echo " ./build.sh -prod -secure # Build prod without automation"
echo " ./build.sh -install # Build and open Finder to drag to Applications"
echo " ./build.sh logs # View latest console output"
exit 0
;;
*)
error "Unknown option: $1. Use --help for usage."
;;
esac
done
# Handle logs command
if [ "$SHOW_LOGS" = true ]; then
if [ -f "$CONSOLE_DIR/console_latest.log" ]; then
log "Latest console log:"
echo "----------------------------------------"
tail -100 "$CONSOLE_DIR/console_latest.log"
else
warn "No console logs found at $CONSOLE_DIR/console_latest.log"
fi
exit 0
fi
# Handle clean-logs command
if [ "$CLEAN_LOGS" = true ]; then
log "Cleaning logs..."
rm -rf "$LOGS_DIR" "$CONSOLE_DIR"
success "Logs cleaned!"
exit 0
fi
# Handle clean command
if [ "$CLEAN_BUILD" = true ]; then
log "Cleaning build folder (preserving logs)..."
# Backup logs
if [ -d "$LOGS_DIR" ]; then
mv "$LOGS_DIR" "$SCRIPT_DIR/.logs_backup" 2>/dev/null || true
fi
if [ -d "$CONSOLE_DIR" ]; then
mv "$CONSOLE_DIR" "$SCRIPT_DIR/.console_backup" 2>/dev/null || true
fi
# Clean build dir
rm -rf "$BUILD_DIR"
# Clean other build artifacts
rm -rf "$SCRIPT_DIR/dist"
rm -rf "$SCRIPT_DIR/.quasar"
rm -rf "$TAURI_DIR/target"
# Restore logs
mkdir -p "$BUILD_DIR"
if [ -d "$SCRIPT_DIR/.logs_backup" ]; then
mv "$SCRIPT_DIR/.logs_backup" "$LOGS_DIR"
fi
if [ -d "$SCRIPT_DIR/.console_backup" ]; then
mv "$SCRIPT_DIR/.console_backup" "$CONSOLE_DIR"
fi
success "Build folder cleaned!"
exit 0
fi
# Require a build mode if not running a command
if [ -z "$BUILD_MODE" ]; then
error "No build mode specified. Use -dev or -prod. See --help for usage."
fi
# Create log directories
mkdir -p "$LOGS_DIR"
mkdir -p "$CONSOLE_DIR"
# Set up logging
BUILD_LOG="$LOGS_DIR/build_${BUILD_MODE}_$TIMESTAMP.log"
CONSOLE_LOG="$CONSOLE_DIR/console_$TIMESTAMP.log"
# Create symlinks to latest logs
ln -sf "build_${BUILD_MODE}_$TIMESTAMP.log" "$LOGS_DIR/build_latest.log"
ln -sf "console_$TIMESTAMP.log" "$CONSOLE_DIR/console_latest.log"
# Logging function that writes to both console and file
log_both() {
echo -e "${BLUE}[Build]${NC} $1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$BUILD_LOG"
}
# Set paths based on build mode
if [ "$BUILD_MODE" = "prod" ]; then
APP_BUNDLE="$PROD_BUILD_DIR/$APP_NAME.app"
INSTALL_BUNDLE="$PROD_INSTALL_DIR/$APP_NAME.app"
BUNDLE_ID="$BUNDLE_ID_BASE"
else
APP_BUNDLE="$DEV_BUILD_DIR/$APP_NAME.app"
INSTALL_BUNDLE=""
BUNDLE_ID="${BUNDLE_ID_BASE}.dev"
fi
log_both "=========================================="
log_both "Building $APP_NAME ($BUILD_MODE mode)"
log_both "=========================================="
log_both "Build log: $BUILD_LOG"
log_both "Console log: $CONSOLE_LOG"
# Create build directories
mkdir -p "$DEV_BUILD_DIR"
mkdir -p "$PROD_BUILD_DIR"
# Kill existing instances before building
log_both "Stopping existing instances..."
pkill -f "$DEV_BUILD_DIR/$APP_NAME.app/Contents/MacOS" 2>/dev/null || true
pkill -f "$PROD_BUILD_DIR/$APP_NAME.app/Contents/MacOS" 2>/dev/null || true
pkill -f "$PROD_INSTALL_DIR/$APP_NAME.app/Contents/MacOS" 2>/dev/null || true
sleep 1
# Check dependencies
command -v node >/dev/null 2>&1 || error "Node.js is required but not installed"
command -v cargo >/dev/null 2>&1 || error "Rust/Cargo is required but not installed"
# Step 1: Install npm dependencies if needed
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
log_both "Installing npm dependencies..."
npm install 2>&1 | tee -a "$BUILD_LOG"
fi
# Step 2: Build with Tauri (builds frontend + Rust + creates bundle)
log_both "Building with Tauri (this embeds frontend into binary)..."
cd "$SCRIPT_DIR"
# Build cargo args for secure mode (no automation)
CARGO_ARGS=""
if [ "$SECURE_BUILD" = true ]; then
log_both "Secure build: disabling automation feature..."
CARGO_ARGS="-- --no-default-features --features custom-protocol"
fi
if [ "$SHOW_INSTALL" = true ]; then
# No --ci flag: opens Finder window for drag-to-Applications
npx tauri build $CARGO_ARGS 2>&1 | tee -a "$BUILD_LOG"
else
# --ci flag: suppress Finder window
npx tauri build --ci $CARGO_ARGS 2>&1 | tee -a "$BUILD_LOG"
fi
# Find the generated app bundle
TAURI_BUNDLE_DIR="$TAURI_DIR/target/release/bundle/macos"
TAURI_APP_BUNDLE="$TAURI_BUNDLE_DIR/$APP_NAME.app"
if [ ! -d "$TAURI_APP_BUNDLE" ]; then
error "Tauri build failed - app bundle not found at $TAURI_APP_BUNDLE"
fi
# Step 3: Copy the Tauri-built bundle to our build directory
log_both "Copying app bundle to $APP_BUNDLE..."
rm -rf "$APP_BUNDLE"
cp -R "$TAURI_APP_BUNDLE" "$APP_BUNDLE"
# Step 4: Update Info.plist with our bundle ID (dev vs prod)
log_both "Updating Info.plist with bundle ID: $BUNDLE_ID..."
if [ "$BUILD_MODE" = "dev" ]; then
DISPLAY_NAME="$APP_NAME (Dev)"
else
DISPLAY_NAME="$APP_NAME"
fi
# Use PlistBuddy to update the plist
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$APP_BUNDLE/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $DISPLAY_NAME" "$APP_BUNDLE/Contents/Info.plist" 2>/dev/null || \
/usr/libexec/PlistBuddy -c "Add :CFBundleDisplayName string $DISPLAY_NAME" "$APP_BUNDLE/Contents/Info.plist"
# Step 5: For prod, also install to ~/Applications
if [ "$BUILD_MODE" = "prod" ] && [ -n "$INSTALL_BUNDLE" ]; then
log_both "Installing to $INSTALL_BUNDLE..."
pkill -f "$INSTALL_BUNDLE/Contents/MacOS" 2>/dev/null || true
sleep 1
rm -rf "$INSTALL_BUNDLE"
mkdir -p "$PROD_INSTALL_DIR"
cp -R "$APP_BUNDLE" "$INSTALL_BUNDLE"
touch "$INSTALL_BUNDLE"
fi
# Touch the app to update Finder cache
touch "$APP_BUNDLE"
# Build complete
log_both "=========================================="
success "Build Complete!"
log_both "=========================================="
echo ""
echo " Build folder: $BUILD_DIR"
echo " App bundle: $APP_BUNDLE"
echo " Mode: $BUILD_MODE"
echo " Bundle ID: $BUNDLE_ID"
if [ "$SECURE_BUILD" = true ]; then
echo " Automation: DISABLED (secure build)"
else
echo " Automation: enabled (port 9876)"
fi
if [ "$BUILD_MODE" = "prod" ] && [ -n "$INSTALL_BUNDLE" ]; then
echo " Installed to: $INSTALL_BUNDLE"
fi
echo ""
echo " Build log: $BUILD_LOG"
echo " Console log: $CONSOLE_LOG"
echo ""
# Open the app and capture console output
if [ "$OPEN_APP" = true ]; then
log_both "Opening app (console output will be logged)..."
# Run the app in background with output to console log
"$APP_BUNDLE/Contents/MacOS/$BINARY_NAME" >> "$CONSOLE_LOG" 2>&1 &
APP_PID=$!
echo " App started (PID: $APP_PID)"
echo " Console log: $CONSOLE_LOG"
echo ""
echo " View logs with: ./build.sh logs"
else
echo " To run: open \"$APP_BUNDLE\""
echo " Or with logging: \"$APP_BUNDLE/Contents/MacOS/$BINARY_NAME\" 2>&1 | tee -a \"$CONSOLE_LOG\""
fi
echo ""

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri Starter</title>
</head>
<body>
<div id="q-app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
{
"name": "tauri-starter",
"version": "0.1.0",
"description": "Quasar + Vite + Tauri starter template",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"tauri:dev": "tauri dev",
"tauri:build": "tauri build"
},
"dependencies": {
"@quasar/extras": "^1.16.9",
"pinia": "^2.1.7",
"quasar": "^2.14.2",
"vue": "^3.4.18",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@quasar/vite-plugin": "^1.10.0",
"@tauri-apps/api": "^1.5.3",
"@tauri-apps/cli": "^1.5.11",
"@types/node": "^20.11.16",
"@vitejs/plugin-vue": "^5.2.4",
"sass": "^1.70.0",
"typescript": "^5.3.3",
"vite": "^5.0.12"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
[package]
name = "tauri-starter"
version = "0.1.0"
description = "Tauri Starter App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
[build-dependencies]
tauri-build = { version = "1.5", features = [] }
[dependencies]
tauri = { version = "1.6", features = ["shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4"
tiny_http = "0.12" # For automation HTTP server
[features]
default = ["custom-protocol", "automation"]
custom-protocol = ["tauri/custom-protocol"]
automation = [] # Enable automation HTTP server on port 9876

View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,166 @@
# Tauri Automation Plugin
A lightweight, reusable automation layer for Tauri applications. Enables external tools to interact with your app via HTTP API.
## Features
- **HTTP API** on `localhost:9876` for external automation
- **Navigation**: Route to pages
- **Element interaction**: Click, type, clear, select
- **Reading**: Get text, values, attributes, check existence
- **Waiting**: Wait for elements with timeout
- **Screenshots**: Capture page as PNG
- **Debug mode only**: Automatically disabled in release builds
## Architecture
```
┌─────────────────┐ HTTP ┌──────────────────┐
│ External Tool │ ───────────▶ │ Rust HTTP Server │
│ (curl, tests) │ │ (port 9876) │
└─────────────────┘ └────────┬─────────┘
│
│ Tauri Command
▼
┌──────────────────┐
│ JavaScript │
│ Automation │
│ Service │
└────────┬─────────┘
│
│ DOM APIs
▼
┌──────────────────┐
│ WebView │
│ (Your App) │
└──────────────────┘
```
## HTTP API
### Endpoints
| Method | Path | Description |
|--------|------|-------------|
| POST | `/automation/execute` | Execute a command |
| GET | `/automation/screenshot` | Capture screenshot (PNG) |
| GET | `/automation/health` | Health check |
### Commands
```bash
# Navigate to a route
curl -X POST http://localhost:9876/automation/execute \
-H "Content-Type: application/json" \
-d '{"command": "navigate", "args": {"path": "/profile"}}'
# Click an element
curl -X POST http://localhost:9876/automation/execute \
-H "Content-Type: application/json" \
-d '{"command": "click", "args": {"selector": "#submit-btn"}}'
# Type into an input
curl -X POST http://localhost:9876/automation/execute \
-H "Content-Type: application/json" \
-d '{"command": "type", "args": {"selector": "#email", "text": "test@example.com"}}'
# Get element text
curl -X POST http://localhost:9876/automation/execute \
-H "Content-Type: application/json" \
-d '{"command": "getText", "args": {"selector": ".heading"}}'
# Wait for element
curl -X POST http://localhost:9876/automation/execute \
-H "Content-Type: application/json" \
-d '{"command": "waitFor", "args": {"selector": ".loaded", "timeout": 5000}}'
# Take screenshot
curl http://localhost:9876/automation/screenshot --output screenshot.png
# Health check
curl http://localhost:9876/automation/health
```
### Available Commands
| Command | Args | Returns | Description |
|---------|------|---------|-------------|
| `navigate` | `{path}` | `null` | Navigate to route |
| `click` | `{selector}` | `null` | Click element |
| `type` | `{selector, text}` | `null` | Type into input |
| `clear` | `{selector}` | `null` | Clear input value |
| `getText` | `{selector}` | `string` | Get element text |
| `getValue` | `{selector}` | `string` | Get input value |
| `getAttribute` | `{selector, attribute}` | `string` | Get attribute |
| `exists` | `{selector}` | `boolean` | Check if element exists |
| `waitFor` | `{selector, timeout?}` | `boolean` | Wait for element (default 5s) |
| `select` | `{selector, value}` | `null` | Select dropdown option |
| `check` | `{selector}` | `null` | Check checkbox |
| `uncheck` | `{selector}` | `null` | Uncheck checkbox |
| `getUrl` | `{}` | `string` | Get current URL |
| `getTitle` | `{}` | `string` | Get page title |
| `eval` | `{script}` | `any` | Evaluate JavaScript |
## Installation (Adding to Another Project)
### 1. Copy Rust files
Copy `src-tauri/src/automation/` to your project's `src-tauri/src/`.
### 2. Update Cargo.toml
Add dependencies:
```toml
[dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
warp = "0.3"
serde_json = "1.0"
```
### 3. Update main.rs
```rust
#[cfg(debug_assertions)]
mod automation;
fn main() {
let mut builder = tauri::Builder::default();
#[cfg(debug_assertions)]
{
builder = builder
.plugin(automation::init())
.invoke_handler(tauri::generate_handler![
automation::commands::execute_automation
]);
}
builder
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
### 4. Copy JS files
Copy `src/automation/` to your project's `src/`.
### 5. Initialize in App.vue
```typescript
import { initAutomation } from './automation'
onMounted(() => {
initAutomation()
})
```
## Security
- **Debug builds only**: The HTTP server only runs in debug builds (`#[cfg(debug_assertions)]`)
- **Localhost only**: Server binds to `127.0.0.1`, not accessible externally
- **No auth**: Designed for local development/testing only
## License
MIT - Use freely in your projects.

View File

@ -0,0 +1,78 @@
//! Tauri Automation Plugin
//!
//! Provides HTTP API for external automation of the application.
//! Enabled via the "automation" feature flag.
pub mod server;
use std::sync::{Arc, Mutex};
use tauri::{
plugin::{Builder, TauriPlugin},
Manager, Runtime, Window,
};
/// Shared state for automation
pub struct AutomationState {
pub window: Arc<Mutex<Option<Window<tauri::Wry>>>>,
}
impl Default for AutomationState {
fn default() -> Self {
Self {
window: Arc::new(Mutex::new(None)),
}
}
}
/// Initialize the automation plugin
pub fn init() -> TauriPlugin<tauri::Wry> {
Builder::new("automation")
.setup(|app| {
let state = AutomationState::default();
app.manage(state);
// Start HTTP server in background thread
let app_handle = app.clone();
std::thread::spawn(move || {
server::start_server(app_handle);
});
println!("[Automation] Plugin initialized - HTTP server starting on port 9876");
Ok(())
})
.build()
}
/// Execute an automation command via JavaScript evaluation
pub fn execute_command(window: &Window<tauri::Wry>, command: &str, args: &serde_json::Value) -> Result<serde_json::Value, String> {
let args_json = serde_json::to_string(args).map_err(|e| e.to_string())?;
let script = format!(
r#"
(async function() {{
try {{
if (typeof window.__TAURI_AUTOMATION__ === 'undefined') {{
return JSON.stringify({{ error: 'Automation not initialized. Call initAutomation() first.' }});
}}
const result = await window.__TAURI_AUTOMATION__.execute('{}', {});
return JSON.stringify({{ success: true, result: result }});
}} catch (e) {{
return JSON.stringify({{ error: e.message || String(e) }});
}}
}})()
"#,
command,
args_json
);
// Execute JavaScript and get result
let result = window.eval(&script);
match result {
Ok(_) => {
// For Tauri 1.x, eval doesn't return a value directly
Ok(serde_json::json!({ "success": true, "note": "Command sent to webview" }))
}
Err(e) => Err(format!("Failed to execute script: {}", e))
}
}

View File

@ -0,0 +1,326 @@
//! HTTP server for automation commands
use std::io::Read;
use std::sync::Mutex;
use tauri::{AppHandle, Manager};
use tiny_http::{Server, Response, Header, Method};
const PORT: u16 = 9876;
// Global screenshot buffer (simple approach without lazy_static)
static SCREENSHOT_DATA: Mutex<Option<String>> = Mutex::new(None);
pub fn set_screenshot_data(data: String) {
if let Ok(mut guard) = SCREENSHOT_DATA.lock() {
*guard = Some(data);
}
}
pub fn take_screenshot_data() -> Option<String> {
if let Ok(mut guard) = SCREENSHOT_DATA.lock() {
guard.take()
} else {
None
}
}
/// Start the HTTP server
pub fn start_server(app_handle: AppHandle<tauri::Wry>) {
let addr = format!("127.0.0.1:{}", PORT);
let server = match Server::http(&addr) {
Ok(s) => s,
Err(e) => {
eprintln!("[Automation] Failed to start server on {}: {}", addr, e);
return;
}
};
println!("[Automation] HTTP server listening on http://{}", addr);
for mut request in server.incoming_requests() {
let method = request.method().clone();
let url = request.url().to_string();
println!("[Automation] {} {}", method, url);
let response = match (&method, url.as_str()) {
// Health check
(&Method::Get, "/automation/health") => {
json_response(serde_json::json!({
"status": "ok",
"port": PORT,
"version": "1.0.0"
}))
}
// Execute command
(&Method::Post, "/automation/execute") => {
handle_execute(&app_handle, &mut request)
}
// Screenshot
(&Method::Get, "/automation/screenshot") => {
handle_screenshot(&app_handle)
}
// CORS preflight
(&Method::Options, _) => {
cors_response()
}
// 404
_ => {
json_response_with_status(
serde_json::json!({ "error": "Not found" }),
404
)
}
};
if let Err(e) = request.respond(response) {
eprintln!("[Automation] Failed to send response: {}", e);
}
}
}
/// Handle execute command request
fn handle_execute(
app_handle: &AppHandle<tauri::Wry>,
request: &mut tiny_http::Request,
) -> Response<std::io::Cursor<Vec<u8>>> {
// Read body
let mut body = String::new();
if let Err(e) = request.as_reader().read_to_string(&mut body) {
return json_response_with_status(
serde_json::json!({ "error": format!("Failed to read body: {}", e) }),
400
);
}
// Parse JSON
let payload: serde_json::Value = match serde_json::from_str(&body) {
Ok(v) => v,
Err(e) => {
return json_response_with_status(
serde_json::json!({ "error": format!("Invalid JSON: {}", e) }),
400
);
}
};
let command = match payload.get("command").and_then(|v| v.as_str()) {
Some(c) => c.to_string(),
None => {
return json_response_with_status(
serde_json::json!({ "error": "Missing 'command' field" }),
400
);
}
};
let args = payload.get("args").cloned().unwrap_or(serde_json::json!({}));
// Get the main window
let window = match app_handle.get_window("main") {
Some(w) => w,
None => {
return json_response_with_status(
serde_json::json!({ "error": "Main window not found" }),
500
);
}
};
// Build JavaScript to execute
let args_json = serde_json::to_string(&args).unwrap_or_else(|_| "{}".to_string());
let script = format!(
r#"
(async function() {{
if (typeof window.__TAURI_AUTOMATION__ === 'undefined') {{
console.error('[Automation] Not initialized');
return;
}}
try {{
const result = await window.__TAURI_AUTOMATION__.execute('{}', {});
window.__TAURI_AUTOMATION__._lastResult = {{ success: true, result: result }};
}} catch (e) {{
window.__TAURI_AUTOMATION__._lastResult = {{ success: false, error: e.message || String(e) }};
}}
}})();
"#,
command,
args_json
);
// Execute the script
if let Err(e) = window.eval(&script) {
return json_response_with_status(
serde_json::json!({ "error": format!("Script execution failed: {}", e) }),
500
);
}
// Wait a bit for async commands to complete
std::thread::sleep(std::time::Duration::from_millis(100));
// Return success - the result is stored in the webview for debugging
json_response(serde_json::json!({
"success": true,
"message": "Command executed",
"command": command
}))
}
/// Handle screenshot request
fn handle_screenshot(app_handle: &AppHandle<tauri::Wry>) -> Response<std::io::Cursor<Vec<u8>>> {
let window = match app_handle.get_window("main") {
Some(w) => w,
None => {
return json_response_with_status(
serde_json::json!({ "error": "Main window not found" }),
500
);
}
};
// Request screenshot from JS
let script = r#"
(async function() {
if (typeof window.__TAURI_AUTOMATION__ === 'undefined') {
console.error('[Automation] Not initialized');
return;
}
try {
await window.__TAURI_AUTOMATION__.captureAndSend();
} catch (e) {
console.error('[Automation] Screenshot failed:', e);
}
})();
"#;
if let Err(e) = window.eval(script) {
return json_response_with_status(
serde_json::json!({ "error": format!("Screenshot request failed: {}", e) }),
500
);
}
// Wait for JS to send the screenshot data
// html2canvas can take a while, especially on first load
std::thread::sleep(std::time::Duration::from_millis(2000));
// Check if we have screenshot data
if let Some(data_url) = take_screenshot_data() {
// Parse data URL: data:image/png;base64,....
if let Some(base64_data) = data_url.strip_prefix("data:image/png;base64,") {
match base64_decode(base64_data) {
Ok(bytes) => {
return png_response(bytes);
}
Err(e) => {
return json_response_with_status(
serde_json::json!({ "error": format!("Base64 decode failed: {}", e) }),
500
);
}
}
}
}
json_response_with_status(
serde_json::json!({ "error": "Screenshot not available. Make sure html2canvas is loaded." }),
500
)
}
/// Simple base64 decoder
fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
let input = input.trim();
let chars: Vec<char> = input.chars().filter(|c| !c.is_whitespace()).collect();
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut output = Vec::new();
let mut buffer: u32 = 0;
let mut bits_collected = 0;
for c in chars {
if c == '=' {
break;
}
let value = ALPHABET.iter().position(|&x| x == c as u8)
.ok_or_else(|| format!("Invalid base64 character: {}", c))? as u32;
buffer = (buffer << 6) | value;
bits_collected += 6;
if bits_collected >= 8 {
bits_collected -= 8;
output.push((buffer >> bits_collected) as u8);
buffer &= (1 << bits_collected) - 1;
}
}
Ok(output)
}
/// Create a JSON response
fn json_response(data: serde_json::Value) -> Response<std::io::Cursor<Vec<u8>>> {
json_response_with_status(data, 200)
}
/// Create a JSON response with status
fn json_response_with_status(data: serde_json::Value, status: u16) -> Response<std::io::Cursor<Vec<u8>>> {
let body = serde_json::to_vec(&data).unwrap_or_else(|_| b"{}".to_vec());
let len = body.len();
let cursor = std::io::Cursor::new(body);
Response::new(
tiny_http::StatusCode(status),
vec![
Header::from_bytes(&b"Content-Type"[..], &b"application/json"[..]).unwrap(),
Header::from_bytes(&b"Access-Control-Allow-Origin"[..], &b"*"[..]).unwrap(),
Header::from_bytes(&b"Access-Control-Allow-Methods"[..], &b"GET, POST, OPTIONS"[..]).unwrap(),
Header::from_bytes(&b"Access-Control-Allow-Headers"[..], &b"Content-Type"[..]).unwrap(),
],
cursor,
Some(len),
None,
)
}
/// Create a PNG response
fn png_response(data: Vec<u8>) -> Response<std::io::Cursor<Vec<u8>>> {
let len = data.len();
let cursor = std::io::Cursor::new(data);
Response::new(
tiny_http::StatusCode(200),
vec![
Header::from_bytes(&b"Content-Type"[..], &b"image/png"[..]).unwrap(),
Header::from_bytes(&b"Access-Control-Allow-Origin"[..], &b"*"[..]).unwrap(),
],
cursor,
Some(len),
None,
)
}
/// CORS preflight response
fn cors_response() -> Response<std::io::Cursor<Vec<u8>>> {
let cursor = std::io::Cursor::new(Vec::new());
Response::new(
tiny_http::StatusCode(204),
vec![
Header::from_bytes(&b"Access-Control-Allow-Origin"[..], &b"*"[..]).unwrap(),
Header::from_bytes(&b"Access-Control-Allow-Methods"[..], &b"GET, POST, OPTIONS"[..]).unwrap(),
Header::from_bytes(&b"Access-Control-Allow-Headers"[..], &b"Content-Type"[..]).unwrap(),
],
cursor,
Some(0),
None,
)
}

View File

@ -0,0 +1,52 @@
// Prevents additional console window on Windows in release
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use chrono::Local;
// Automation module - enabled via feature flag
#[cfg(feature = "automation")]
mod automation;
/// Log frontend console messages to stdout with timestamp
#[tauri::command]
fn log_to_stdout(level: String, message: String) {
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
println!("[{}] [{}] {}", timestamp, level, message);
}
/// Example command - replace with your own
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! Welcome to Tauri.", name)
}
/// Receive screenshot data from JS automation
#[cfg(feature = "automation")]
#[tauri::command]
fn automation_screenshot_data(data: String) {
automation::server::set_screenshot_data(data);
}
#[cfg(feature = "automation")]
fn main() {
tauri::Builder::default()
.plugin(automation::init())
.invoke_handler(tauri::generate_handler![
log_to_stdout,
greet,
automation_screenshot_data
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[cfg(not(feature = "automation"))]
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
log_to_stdout,
greet
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@ -0,0 +1,46 @@
{
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
"devPath": "http://localhost:9000",
"distDir": "../dist"
},
"package": {
"productName": "TauriStarter",
"version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": false,
"shell": {
"open": true
}
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauristarter.app",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
},
"security": {
"csp": null
},
"windows": [
{
"fullscreen": false,
"resizable": true,
"title": "Tauri Starter",
"width": 1200,
"height": 800,
"minWidth": 800,
"minHeight": 600
}
]
}
}

View File

@ -0,0 +1,7 @@
<template>
<router-view />
</template>
<script setup lang="ts">
// App root component
</script>

View File

@ -0,0 +1,141 @@
# Automation Service (JavaScript)
Frontend automation service that handles DOM manipulation commands from the Rust backend.
## Files
| File | Purpose |
|------|---------|
| `index.ts` | Main service initialization and screenshot capture |
| `commands.ts` | All DOM automation commands |
## Setup
Initialize in your app's entry point (App.vue or main.ts):
```typescript
import { initAutomation } from './automation'
// In your app setup (only runs in dev)
initAutomation()
```
## Available Commands
### Navigation
- `navigate` - Navigate to a Vue Router path
### Element Interaction
- `click` - Click an element
- `type` - Type text into an input
- `clear` - Clear an input value
- `select` - Select dropdown option
- `check` - Check a checkbox
- `uncheck` - Uncheck a checkbox
- `focus` - Focus an element
- `blur` - Blur an element
- `pressKey` - Simulate key press
- `submit` - Submit a form
- `scrollTo` - Scroll to element
### Reading
- `getText` - Get element text content
- `getValue` - Get input value
- `getAttribute` - Get element attribute
- `exists` - Check if element exists
- `getElements` - Get all matching elements
- `getHtml` - Get element HTML
- `getUrl` - Get current URL
- `getTitle` - Get page title
### Waiting
- `waitFor` - Wait for element to appear
- `wait` - Wait for specified milliseconds
### Advanced
- `eval` - Execute arbitrary JavaScript
## Adding Custom Commands
Add new commands to `commands.ts`:
```typescript
export const commands: Record<string, CommandHandler> = {
// ... existing commands
async myCustomCommand(args): Promise<void> {
const value = args.value as string
// Your implementation
},
}
```
## Screenshot Support
Screenshots use html2canvas, loaded dynamically from CDN:
- https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
For offline use, bundle html2canvas:
```bash
npm install html2canvas
```
Then update `index.ts` to import directly instead of CDN loading.
## Vue Router Integration
The `navigate` command tries to use Vue Router. For this to work, you need to expose the router on the window object. Update your `main.ts`:
```typescript
const app = createApp(App)
app.use(router)
// Expose for automation
if (import.meta.env.DEV) {
(window as any).__VUE_APP__ = app
}
app.mount('#app')
```
## Selectors
All selector arguments use standard CSS selectors:
```bash
# By ID
{"selector": "#my-id"}
# By class
{"selector": ".my-class"}
# By attribute
{"selector": "[data-testid='submit']"}
# Combined
{"selector": "form#login .btn-primary"}
# Quasar components often need to target inner elements
{"selector": ".q-input input"}
{"selector": ".q-btn"}
```
## Tips for Quasar
Quasar components have specific DOM structures:
| Component | Selector for inner element |
|-----------|---------------------------|
| q-input | `.q-input input` or `.q-input textarea` |
| q-select | `.q-select` (click to open) |
| q-btn | `.q-btn` |
| q-checkbox | `.q-checkbox` |
| q-tab | `.q-tab` |
Use `data-testid` attributes for reliable selectors:
```vue
<q-btn data-testid="submit-btn" label="Submit" />
```
```bash
{"selector": "[data-testid='submit-btn']"}
```

View File

@ -0,0 +1,387 @@
/**
* Automation Commands
*
* DOM manipulation commands for automated testing.
*/
export type CommandArgs = Record<string, unknown>
export type CommandResult = unknown
type CommandHandler = (args: CommandArgs) => Promise<CommandResult>
// Router type for type safety
interface VueRouter {
push: (path: string) => Promise<unknown>
currentRoute: { value: { path: string } }
}
// Get Vue Router instance from the app
function getRouter(): VueRouter | undefined {
// Access the router from the app instance stored on window
const app = (window as unknown as { __VUE_APP__?: { config: { globalProperties: { $router: VueRouter } } } }).__VUE_APP__
return app?.config.globalProperties.$router
}
/**
* Wait for an element to appear in the DOM
*/
async function waitForElement(selector: string, timeout = 5000): Promise<Element | null> {
const start = Date.now()
while (Date.now() - start < timeout) {
const element = document.querySelector(selector)
if (element) return element
await new Promise(r => setTimeout(r, 100))
}
return null
}
/**
* Get an element or throw
*/
function getElement(selector: string): Element {
const element = document.querySelector(selector)
if (!element) {
throw new Error(`Element not found: ${selector}`)
}
return element
}
/**
* Simulate realistic typing into an input
*/
function simulateTyping(element: HTMLInputElement | HTMLTextAreaElement, text: string): void {
element.focus()
element.value = text
// Dispatch events to trigger Vue reactivity
element.dispatchEvent(new Event('input', { bubbles: true }))
element.dispatchEvent(new Event('change', { bubbles: true }))
}
/**
* Available automation commands
*/
export const commands: Record<string, CommandHandler> = {
/**
* Navigate to a route
*/
async navigate(args): Promise<void> {
const path = args.path as string
if (!path) throw new Error('Missing path argument')
const router = getRouter()
if (router) {
await router.push(path)
} else {
// Fallback: use window.location
window.location.hash = path
}
// Wait for navigation to complete
await new Promise(r => setTimeout(r, 100))
},
/**
* Click an element
*/
async click(args): Promise<void> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = await waitForElement(selector, 5000)
if (!element) throw new Error(`Element not found: ${selector}`)
// Scroll into view
element.scrollIntoView({ behavior: 'instant', block: 'center' })
await new Promise(r => setTimeout(r, 50))
// Click
;(element as HTMLElement).click()
// Wait for any resulting actions
await new Promise(r => setTimeout(r, 100))
},
/**
* Type text into an input
*/
async type(args): Promise<void> {
const selector = args.selector as string
const text = args.text as string
if (!selector) throw new Error('Missing selector argument')
if (text === undefined) throw new Error('Missing text argument')
const element = await waitForElement(selector, 5000)
if (!element) throw new Error(`Element not found: ${selector}`)
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
simulateTyping(element, text)
} else {
throw new Error(`Element is not an input: ${selector}`)
}
},
/**
* Clear an input
*/
async clear(args): Promise<void> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = getElement(selector)
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
simulateTyping(element, '')
} else {
throw new Error(`Element is not an input: ${selector}`)
}
},
/**
* Get element text content
*/
async getText(args): Promise<string> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = await waitForElement(selector, 5000)
if (!element) throw new Error(`Element not found: ${selector}`)
return element.textContent?.trim() || ''
},
/**
* Get input value
*/
async getValue(args): Promise<string> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = getElement(selector)
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
return element.value
}
throw new Error(`Element is not an input: ${selector}`)
},
/**
* Get element attribute
*/
async getAttribute(args): Promise<string | null> {
const selector = args.selector as string
const attribute = args.attribute as string
if (!selector) throw new Error('Missing selector argument')
if (!attribute) throw new Error('Missing attribute argument')
const element = getElement(selector)
return element.getAttribute(attribute)
},
/**
* Check if element exists
*/
async exists(args): Promise<boolean> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
return document.querySelector(selector) !== null
},
/**
* Wait for element to appear
*/
async waitFor(args): Promise<boolean> {
const selector = args.selector as string
const timeout = (args.timeout as number) || 5000
if (!selector) throw new Error('Missing selector argument')
const element = await waitForElement(selector, timeout)
return element !== null
},
/**
* Select dropdown option
*/
async select(args): Promise<void> {
const selector = args.selector as string
const value = args.value as string
if (!selector) throw new Error('Missing selector argument')
if (value === undefined) throw new Error('Missing value argument')
const element = getElement(selector)
if (element instanceof HTMLSelectElement) {
element.value = value
element.dispatchEvent(new Event('change', { bubbles: true }))
} else {
throw new Error(`Element is not a select: ${selector}`)
}
},
/**
* Check a checkbox
*/
async check(args): Promise<void> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = getElement(selector)
if (element instanceof HTMLInputElement && element.type === 'checkbox') {
if (!element.checked) {
element.click()
}
} else {
throw new Error(`Element is not a checkbox: ${selector}`)
}
},
/**
* Uncheck a checkbox
*/
async uncheck(args): Promise<void> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = getElement(selector)
if (element instanceof HTMLInputElement && element.type === 'checkbox') {
if (element.checked) {
element.click()
}
} else {
throw new Error(`Element is not a checkbox: ${selector}`)
}
},
/**
* Get current URL/route
*/
async getUrl(): Promise<string> {
return window.location.href
},
/**
* Get page title
*/
async getTitle(): Promise<string> {
return document.title
},
/**
* Evaluate arbitrary JavaScript
*/
async eval(args): Promise<unknown> {
const script = args.script as string
if (!script) throw new Error('Missing script argument')
// Use Function constructor to evaluate in global scope
const fn = new Function(`return (async () => { ${script} })()`)
return await fn()
},
/**
* Get all elements matching selector
*/
async getElements(args): Promise<{ count: number; texts: string[] }> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const elements = document.querySelectorAll(selector)
const texts = Array.from(elements).map(el => el.textContent?.trim() || '')
return { count: elements.length, texts }
},
/**
* Scroll to element
*/
async scrollTo(args): Promise<void> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = getElement(selector)
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
await new Promise(r => setTimeout(r, 300))
},
/**
* Get page HTML
*/
async getHtml(args): Promise<string> {
const selector = (args.selector as string) || 'body'
const element = getElement(selector)
return element.innerHTML
},
/**
* Wait for a specified time
*/
async wait(args): Promise<void> {
const ms = (args.ms as number) || 1000
await new Promise(r => setTimeout(r, ms))
},
/**
* Focus an element
*/
async focus(args): Promise<void> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = getElement(selector) as HTMLElement
element.focus()
},
/**
* Blur (unfocus) an element
*/
async blur(args): Promise<void> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = getElement(selector) as HTMLElement
element.blur()
},
/**
* Press a key
*/
async pressKey(args): Promise<void> {
const key = args.key as string
const selector = args.selector as string
if (!key) throw new Error('Missing key argument')
const target = selector ? getElement(selector) : document.activeElement || document.body
target.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }))
target.dispatchEvent(new KeyboardEvent('keyup', { key, bubbles: true }))
},
/**
* Submit a form
*/
async submit(args): Promise<void> {
const selector = args.selector as string
if (!selector) throw new Error('Missing selector argument')
const element = getElement(selector)
if (element instanceof HTMLFormElement) {
element.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
} else {
// Find parent form
const form = element.closest('form')
if (form) {
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
} else {
throw new Error('No form found')
}
}
},
}

View File

@ -0,0 +1,116 @@
/**
* Tauri Automation Service
*
* Provides automation capabilities for external testing tools.
* Only active in development builds.
*/
import { invoke } from '@tauri-apps/api/tauri'
import { commands, type CommandArgs, type CommandResult } from './commands'
declare global {
interface Window {
__TAURI_AUTOMATION__: AutomationService
}
}
export interface AutomationService {
execute: (command: string, args: CommandArgs) => Promise<CommandResult>
screenshot: () => Promise<string>
captureAndSend: () => Promise<void>
_lastResult: { success: boolean; result?: unknown; error?: string } | null
}
/**
* Initialize the automation service
* Call this once when your app starts
* Note: Automation is controlled by Rust feature flag, JS side always initializes
*/
export function initAutomation(): void {
console.log('[Automation] Initializing...')
const service: AutomationService = {
_lastResult: null,
/**
* Execute an automation command
*/
async execute(command: string, args: CommandArgs = {}): Promise<CommandResult> {
console.log(`[Automation] Executing: ${command}`, args)
const handler = commands[command]
if (!handler) {
throw new Error(`Unknown command: ${command}`)
}
try {
const result = await handler(args)
console.log(`[Automation] Result:`, result)
return result
} catch (error) {
console.error(`[Automation] Error:`, error)
throw error
}
},
/**
* Capture screenshot and return as data URL
*/
async screenshot(): Promise<string> {
// Dynamically import html2canvas
const html2canvas = await loadHtml2Canvas()
const canvas = await html2canvas(document.body, {
backgroundColor: '#121212',
scale: 1,
logging: false,
useCORS: true,
})
return canvas.toDataURL('image/png')
},
/**
* Capture screenshot and send to Rust backend
*/
async captureAndSend(): Promise<void> {
try {
const dataUrl = await this.screenshot()
await invoke('automation_screenshot_data', { data: dataUrl })
console.log('[Automation] Screenshot sent to backend')
} catch (error) {
console.error('[Automation] Screenshot capture failed:', error)
throw error
}
},
}
// Expose globally for Rust to call via eval
window.__TAURI_AUTOMATION__ = service
console.log('[Automation] Ready. HTTP API available at http://localhost:9876')
}
/**
* Dynamically load html2canvas from CDN
*/
async function loadHtml2Canvas(): Promise<typeof import('html2canvas').default> {
if ((window as unknown as { html2canvas?: unknown }).html2canvas) {
return (window as unknown as { html2canvas: typeof import('html2canvas').default }).html2canvas
}
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'
script.onload = () => {
const h2c = (window as unknown as { html2canvas: typeof import('html2canvas').default }).html2canvas
if (h2c) {
resolve(h2c)
} else {
reject(new Error('html2canvas failed to load'))
}
}
script.onerror = () => reject(new Error('Failed to load html2canvas'))
document.head.appendChild(script)
})
}
export { commands } from './commands'

View File

@ -0,0 +1,52 @@
// Console logger for Tauri - forwards console.log to Rust for file logging
// This allows us to capture frontend logs when running as a desktop app
import { invoke } from '@tauri-apps/api/tauri'
// Check if running in Tauri environment
function isTauri (): boolean {
return typeof window !== 'undefined' && '__TAURI__' in window
}
export function setupConsoleLogger () {
// Only activate in Tauri environment
if (!isTauri()) {
return
}
const originalLog = console.log
const originalWarn = console.warn
const originalError = console.error
const sendToRust = (level: string, args: unknown[]) => {
const message = args.map(arg => {
if (typeof arg === 'object') {
try {
return JSON.stringify(arg)
} catch {
return String(arg)
}
}
return String(arg)
}).join(' ')
invoke('log_to_stdout', { level, message }).catch(() => {
// Silently fail if Rust command not available
})
}
console.log = (...args: unknown[]) => {
originalLog.apply(console, args)
sendToRust('INFO', args)
}
console.warn = (...args: unknown[]) => {
originalWarn.apply(console, args)
sendToRust('WARN', args)
}
console.error = (...args: unknown[]) => {
originalError.apply(console, args)
sendToRust('ERROR', args)
}
}

View File

@ -0,0 +1,2 @@
// App-specific styles
// Add your custom styles here

View File

@ -0,0 +1,14 @@
// Quasar SCSS variables
// Customize colors, breakpoints, etc.
$primary : #1976D2;
$secondary : #26A69A;
$accent : #9C27B0;
$dark : #1D1D1D;
$dark-page : #121212;
$positive : #21BA45;
$negative : #C10015;
$info : #31CCEC;
$warning : #F2C037;

View File

@ -0,0 +1,54 @@
<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn
flat
dense
round
icon="menu"
aria-label="Menu"
@click="toggleLeftDrawer"
/>
<q-toolbar-title>
Tauri Starter
</q-toolbar-title>
</q-toolbar>
</q-header>
<q-drawer
v-model="leftDrawerOpen"
show-if-above
bordered
>
<q-list>
<q-item-label header>
Navigation
</q-item-label>
<q-item clickable v-ripple to="/">
<q-item-section avatar>
<q-icon name="home" />
</q-item-section>
<q-item-section>
Home
</q-item-section>
</q-item>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const leftDrawerOpen = ref(false)
function toggleLeftDrawer () {
leftDrawerOpen.value = !leftDrawerOpen.value
}
</script>

View File

@ -0,0 +1,49 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { Quasar, Notify, Dialog, Loading } from 'quasar'
import router from './router'
import { setupConsoleLogger } from './boot/console-logger'
// Import Quasar css
import '@quasar/extras/roboto-font/roboto-font.css'
import '@quasar/extras/material-icons/material-icons.css'
import 'quasar/src/css/index.sass'
// Import app styles
import './css/app.scss'
// Import root component
import App from './App.vue'
// Setup console logger for Tauri
setupConsoleLogger()
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(Quasar, {
plugins: {
Notify,
Dialog,
Loading
},
config: {
dark: 'auto'
}
})
// Mount the app
app.mount('#q-app')
// Expose Vue app for automation
;(window as unknown as { __VUE_APP__: typeof app }).__VUE_APP__ = app
// Initialize automation service after app is mounted
setTimeout(() => {
import('./automation').then(({ initAutomation }) => {
initAutomation()
}).catch(err => {
console.warn('[Automation] Failed to load:', err)
})
}, 100)

View File

@ -0,0 +1,17 @@
<template>
<q-page class="flex flex-center">
<div class="text-center">
<h1 class="text-h1 text-weight-thin q-mb-md">404</h1>
<p class="text-grey-6 q-mb-lg">Page not found</p>
<q-btn
color="primary"
label="Go Home"
to="/"
/>
</div>
</q-page>
</template>
<script setup lang="ts">
// 404 page
</script>

View File

@ -0,0 +1,34 @@
<template>
<q-page class="flex flex-center">
<div class="text-center">
<h4 class="q-mb-md">Tauri Starter</h4>
<p class="text-grey-6">Quasar + Vite + Tauri template</p>
<q-btn
color="primary"
label="Get Started"
class="q-mt-md"
@click="showMessage"
/>
</div>
</q-page>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar'
import { onMounted } from 'vue'
const $q = useQuasar()
onMounted(() => {
console.log('App initialized')
})
function showMessage () {
console.log('Get Started clicked')
$q.notify({
message: 'Ready to build!',
color: 'positive',
icon: 'check'
})
}
</script>

View File

@ -0,0 +1,22 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/IndexPage.vue') }
]
},
{
path: '/:catchAll(.*)*',
component: () => import('pages/ErrorNotFound.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

View File

@ -0,0 +1,26 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useExampleStore = defineStore('example', () => {
// State
const count = ref(0)
// Getters
const doubleCount = computed(() => count.value * 2)
// Actions
function increment () {
count.value++
}
function reset () {
count.value = 0
}
return {
count,
doubleCount,
increment,
reset
}
})

View File

@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"src/*": ["src/*"],
"components/*": ["src/components/*"],
"layouts/*": ["src/layouts/*"],
"pages/*": ["src/pages/*"],
"stores/*": ["src/stores/*"],
"boot/*": ["src/boot/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist", "src-tauri"]
}

View File

@ -0,0 +1,31 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { quasar, transformAssetUrls } from '@quasar/vite-plugin'
import { fileURLToPath } from 'node:url'
export default defineConfig({
plugins: [
vue({
template: { transformAssetUrls }
}),
quasar({
sassVariables: 'src/css/quasar-variables.scss'
})
],
resolve: {
alias: {
src: fileURLToPath(new URL('./src', import.meta.url)),
components: fileURLToPath(new URL('./src/components', import.meta.url)),
layouts: fileURLToPath(new URL('./src/layouts', import.meta.url)),
pages: fileURLToPath(new URL('./src/pages', import.meta.url)),
stores: fileURLToPath(new URL('./src/stores', import.meta.url)),
boot: fileURLToPath(new URL('./src/boot', import.meta.url))
}
},
server: {
port: 9000,
strictPort: false
},
clearScreen: false,
envPrefix: ['VITE_', 'TAURI_']
})

View File

@ -0,0 +1,50 @@
# Documentation
> Technical documentation for [Project Name]
## Table of Contents
### Getting Started
- [Installation & Setup](./getting-started.md)
### Features
<!-- Add feature docs as they're built. Format: [Feature Name](./feature-name.md) -->
### Architecture
- [Tech Stack & Architecture](./architecture.md)
### Development
- [Development Guide](./development.md)
- [Build & Deployment](./deployment.md)
---
## Documentation Guidelines
### Adding New Documentation
After completing a feature or phase, add documentation:
1. Create a new `.md` file in this folder using the naming convention below
2. Add a link to the Table of Contents above
3. Use the appropriate template as a starting point:
- [Feature template](./TEMPLATE-feature.md) - For documenting features
- [Build plan template](./TEMPLATE-build-plan.md) - For creating structured build plans
### Naming Convention
| Type | Pattern | Example |
|------|---------|---------|
| Feature docs | `feature-[name].md` | `feature-authentication.md` |
| Component docs | `component-[name].md` | `component-sidebar.md` |
| Integration docs | `integration-[name].md` | `integration-nhost.md` |
| Guide docs | `guide-[topic].md` | `guide-testing.md` |
| Build plans | `[name]-build-plan.md` | `auth-build-plan.md` |
### Documentation Structure
Each feature doc should include:
- **Overview** - What the feature does
- **Usage** - How to use it (with code examples)
- **Configuration** - Available options/settings
- **Related** - Links to related features/components

View File

@ -0,0 +1,121 @@
# [Project Name] - Build Plan
## Executor Config
```yaml
default_branch_prefix: feat/phase
test_before_commit: true
require_passing_tests: true
max_test_retries: 3
```
## Phase 1: [Phase Name]
**repo**: [repository-name]
**branch**: feat/phase-1-[slug]
### 1.1 [Task Name]
- **task**: [Description of what to do. Be specific and actionable.
Include context about existing code to use or patterns to follow.]
- **test**: `[command to verify success]`
- **commit**: "[Commit message]"
### 1.2 [Task Name]
- **task**: [Description]
- **test**: `[test command]`
- **commit**: "[Commit message]"
### 1.3 Documentation
- **task**: Update documentation for phase 1 changes.
Add any new files to docs/ folder.
- **test**: none
- **commit**: "Document phase 1"
---
## Phase 2: [Phase Name]
**repo**: [repository-name]
**branch**: feat/phase-2-[slug]
**depends_on**: Phase 1
### 2.1 [Task Name]
- **task**: [Description]
- **test**: `[test command]`
- **commit**: "[Commit message]"
### 2.2 Documentation
- **task**: Update documentation for phase 2 changes.
- **test**: none
- **commit**: "Document phase 2"
---
## Current Status
**Phase**: 1 - [Phase Name]
**Task**: [Current task]
---
## Template Instructions
> Remove this section when using this template
### Plan Format
Each plan consists of:
1. **Executor Config** (optional) - YAML block with settings
2. **Phases** - Major milestones (`## Phase N: Name`)
3. **Tasks** - Individual work items (`### N.N Task Name`)
### Phase Metadata
| Field | Required | Description |
|-------|----------|-------------|
| `**repo**` | Yes | Repository to work in |
| `**branch**` | No | Branch to create (default: `{prefix}-{N}`) |
| `**depends_on**` | No | Phase that must complete first |
### Task Fields
| Field | Required | Description |
|-------|----------|-------------|
| `- **task**` | Yes | What the agent should do |
| `- **test**` | No | Command to verify (`none` to skip) |
| `- **commit**` | No | Commit message (agent decides if omitted) |
### Writing Good Tasks
**Be specific:**
```markdown
# Bad
- **task**: Add authentication
# Good
- **task**: Add JWT authentication with /api/login and /api/logout endpoints.
Use bcrypt for password hashing. Store tokens in httpOnly cookies.
```
**Include context:**
```markdown
- **task**: Create dashboard component at /dashboard showing:
- User's recent activity (last 10 items)
- Account settings link
Use the existing UserCard component for the header.
```
**Use testable tests:**
```markdown
# Bad
- **test**: Check that it works
# Good
- **test**: `pnpm test && pnpm build`
```
### Tips
1. **One thing per task** - Don't combine unrelated work
2. **Tests are checkpoints** - Use them to verify progress
3. **Commits are savepoints** - Small, focused commits
4. **Phases are milestones** - Each should be independently useful
5. **End phases with docs** - Document as you go

View File

@ -0,0 +1,60 @@
# Feature Name
> Brief one-line description of what this feature does
## Overview
Explain what this feature does and why it exists. Include:
- The problem it solves
- Key capabilities
- When to use it
## Usage
### Basic Example
```typescript
// Code example showing basic usage
```
### Advanced Example
```typescript
// Code example showing advanced/common patterns
```
## Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `option1` | `string` | `""` | Description of option |
| `option2` | `boolean` | `false` | Description of option |
## Components
List the main components involved in this feature:
| Component | Location | Purpose |
|-----------|----------|---------|
| `ComponentName` | `src/components/ComponentName.vue` | Brief description |
## Data Flow
Describe how data flows through this feature (if applicable):
```
User Action → Component → Store → API → Database
↑ ↓
UI Update ← ← ← ← Response
```
## Related
- [Related Feature](./feature-related.md)
- [Related Component](./component-related.md)
## Changelog
| Date | Change |
|------|--------|
| YYYY-MM-DD | Initial implementation |

View File

@ -0,0 +1,61 @@
# Architecture
> Technical architecture and design decisions for [Project Name]
## Tech Stack
| Layer | Technology | Purpose |
|-------|------------|---------|
| Frontend | Vue 3 + Quasar | UI framework |
| State | Pinia | State management |
| Routing | Vue Router | Client-side routing |
| Desktop | Tauri | Native desktop wrapper |
| Backend | Nhost | Auth, database, storage |
## Project Structure
```
app/
├── src/
│ ├── boot/ # Initialization scripts
│ ├── components/ # Reusable Vue components
│ ├── composables/ # Vue composables (shared logic)
│ ├── layouts/ # Page layouts
│ ├── pages/ # Route pages
│ ├── router/ # Route definitions
│ ├── stores/ # Pinia stores
│ ├── services/ # API services
│ └── types/ # TypeScript types
├── src-tauri/ # Tauri/Rust code
└── build.sh # Build script
```
## Key Design Decisions
### [Decision 1]
**Context**: [Why this decision was needed]
**Decision**: [What was decided]
**Consequences**: [Trade-offs and implications]
### [Decision 2]
...
## Data Model
[Describe main entities and relationships]
```
Entity1 ──────< Entity2
│
└──────< Entity3
```
## External Integrations
| Service | Purpose | Documentation |
|---------|---------|---------------|
| [Service] | [Purpose] | [Link] |

View File

@ -0,0 +1,78 @@
# Build & Deployment
> Build process and deployment instructions for [Project Name]
## Build Commands
```bash
cd app
# Development build
./build.sh -dev -open
# Production build (installs to ~/Applications)
./build.sh -prod
# Clean build artifacts
./build.sh clean
```
## Build Output
```
app/build/
├── dev/
│ └── [AppName].app # Development build
├── prod/
│ └── [AppName].app # Production build
├── logs/
│ └── build_latest.log # Build output
└── console/
└── console_latest.log # Runtime logs
```
## Production Build
### macOS
1. Build the production version:
```bash
./build.sh -prod
```
2. The app is automatically installed to `~/Applications/`
### Distribution
[Add distribution instructions - notarization, signing, etc.]
## TeamIDE Deployment
If using TeamIDE for hosting:
```bash
# Deploy changes
teamide_deploy
# Set environment variables
teamide_set_env_vars
# Check logs
teamide_get_logs
```
**Workspace ID**: `[your-workspace-id]`
## Environment Variables
### Required for Production
```env
# List production environment variables
```
### Optional
```env
# List optional environment variables
```

View File

@ -0,0 +1,72 @@
# Development Guide
> Development workflow and practices for [Project Name]
## Development Workflow
### Build and Run
This project uses a **build-and-run** approach (not hot reload):
```bash
cd app
./build.sh -dev -open # Build and launch
./build.sh logs # View console output
```
### Why Build-and-Run?
- **Dev = Prod** - What you test is what ships
- **Full logging** - Console output captured to files
- **No dev-only bugs** - Identical to production environment
## Making Changes
### Adding a New Feature
1. Create components in `src/components/`
2. Add pages in `src/pages/`
3. Register routes in `src/router/`
4. Add state management in `src/stores/`
5. **Rebuild to test**: `./build.sh -dev -open`
6. **Document the feature** in `docs/`
### Adding a Tauri Command
1. Add the command in `src-tauri/src/main.rs`
2. Register it in the `invoke_handler`
3. Call from frontend with `invoke('command_name', { args })`
## Debugging
### Console Logs
```bash
./build.sh logs # Last 100 lines
cat build/console/console_latest.log # Full log
```
### Build Errors
```bash
cat build/logs/build_latest.log
```
### Common Issues
| Issue | Solution |
|-------|----------|
| Build fails with cache errors | `./build.sh clean && ./build.sh -dev -open` |
| Changes not appearing | Rebuild - this is build-and-run, not hot reload |
| Console logs missing | Check `console-logger.ts` is in boot files |
## Code Style
- **Components**: PascalCase (`UserProfile.vue`)
- **Composables**: camelCase with `use` prefix (`useAuth.ts`)
- **Stores**: camelCase (`userStore.ts`)
- **Types**: PascalCase (`UserProfile.ts`)
## Testing
[Add testing instructions when implemented]

View File

@ -0,0 +1,52 @@
# Getting Started
> Installation and initial setup guide for [Project Name]
## Prerequisites
- Node.js 18+
- npm
- [Additional prerequisites]
## Installation
```bash
# Clone the repository
git clone https://github.com/dcherrera/[ProjectName].git
cd [ProjectName]/app
# Install dependencies
npm install
```
## Configuration
1. Copy the environment template:
```bash
cp .env.example .env
```
2. Configure required environment variables:
```env
# Required
VARIABLE_NAME=value
```
## Running the App
### Development
```bash
./build.sh -dev -open
```
### Production
```bash
./build.sh -prod
```
## Next Steps
- [Architecture Overview](./architecture.md) - Understand the codebase structure
- [Development Guide](./development.md) - Learn the development workflow

330
templates/build.sh Executable file
View File

@ -0,0 +1,330 @@
#!/bin/bash
#
# Desktop Build Script Template (Tauri)
# Builds the Tauri desktop app for macOS
#
# Usage:
# ./build.sh -dev # Development build (runs from build folder)
# ./build.sh -dev -open # Development build and open app
# ./build.sh -prod # Production build (builds to build/prod/)
# ./build.sh -install # Build and open Finder for drag-to-Applications
# ./build.sh clean # Clean build folder (preserves logs)
# ./build.sh clean-logs # Clean all logs
# ./build.sh logs # View latest console log
#
# ============================================================
# PROJECT CONFIGURATION - Customize these for your project
# ============================================================
APP_NAME="MyApp" # Display name of the app
BUNDLE_ID_BASE="com.myapp.app" # Base bundle ID
VERSION="0.1.0" # App version
BINARY_NAME="my-app" # Rust binary name (usually lowercase with hyphens)
# ============================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "${BLUE}[Build]${NC} $1"; }
success() { echo -e "${GREEN}[Build]${NC} $1"; }
warn() { echo -e "${YELLOW}[Build]${NC} $1"; }
error() { echo -e "${RED}[Build]${NC} $1"; exit 1; }
# Paths
TAURI_DIR="$SCRIPT_DIR/src-tauri"
# Build folder structure
BUILD_DIR="$SCRIPT_DIR/build"
DEV_BUILD_DIR="$BUILD_DIR/dev"
PROD_BUILD_DIR="$BUILD_DIR/prod"
LOGS_DIR="$BUILD_DIR/logs"
CONSOLE_DIR="$BUILD_DIR/console"
PROD_INSTALL_DIR="$HOME/Applications"
# Timestamp for log files
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
# Parse arguments
BUILD_MODE=""
OPEN_APP=false
SHOW_INSTALL=false
CLEAN_BUILD=false
CLEAN_LOGS=false
SHOW_LOGS=false
while [[ $# -gt 0 ]]; do
case $1 in
-prod|--prod|--production)
BUILD_MODE="prod"
shift
;;
-dev|--dev|--development)
BUILD_MODE="dev"
shift
;;
-open|--open)
OPEN_APP=true
shift
;;
-install|--install)
SHOW_INSTALL=true
BUILD_MODE="prod"
shift
;;
clean|--clean)
CLEAN_BUILD=true
shift
;;
clean-logs|--clean-logs)
CLEAN_LOGS=true
shift
;;
logs|--logs)
SHOW_LOGS=true
shift
;;
-h|--help)
echo "$APP_NAME Build Script"
echo ""
echo "Usage: $0 [-dev|-prod] [-open] [clean|clean-logs|logs]"
echo ""
echo "Build Commands:"
echo " -dev, --dev Development build (runs from build folder)"
echo " -prod, --prod Production build (builds to build/prod/)"
echo " -install Build prod and open Finder for drag-to-Applications"
echo " -open, --open Open the app after building"
echo ""
echo "Other Commands:"
echo " clean Clean build artifacts (preserves logs)"
echo " clean-logs Remove all logs"
echo " logs Show latest console log (last 100 lines)"
echo " -h, --help Show this help"
echo ""
echo "Build folders:"
echo " Dev: $DEV_BUILD_DIR"
echo " Prod: $PROD_BUILD_DIR (also installs to $PROD_INSTALL_DIR)"
echo " Logs: $LOGS_DIR"
echo " Console: $CONSOLE_DIR"
echo ""
echo "Examples:"
echo " ./build.sh -dev -open # Build dev and open app"
echo " ./build.sh -prod # Build prod to build/prod/"
echo " ./build.sh -install # Build and open Finder to drag to Applications"
echo " ./build.sh logs # View latest console output"
exit 0
;;
*)
error "Unknown option: $1. Use --help for usage."
;;
esac
done
# Handle logs command
if [ "$SHOW_LOGS" = true ]; then
if [ -f "$CONSOLE_DIR/console_latest.log" ]; then
log "Latest console log:"
echo "----------------------------------------"
tail -100 "$CONSOLE_DIR/console_latest.log"
else
warn "No console logs found at $CONSOLE_DIR/console_latest.log"
fi
exit 0
fi
# Handle clean-logs command
if [ "$CLEAN_LOGS" = true ]; then
log "Cleaning logs..."
rm -rf "$LOGS_DIR" "$CONSOLE_DIR"
success "Logs cleaned!"
exit 0
fi
# Handle clean command
if [ "$CLEAN_BUILD" = true ]; then
log "Cleaning build folder (preserving logs)..."
# Backup logs
if [ -d "$LOGS_DIR" ]; then
mv "$LOGS_DIR" "$SCRIPT_DIR/.logs_backup" 2>/dev/null || true
fi
if [ -d "$CONSOLE_DIR" ]; then
mv "$CONSOLE_DIR" "$SCRIPT_DIR/.console_backup" 2>/dev/null || true
fi
# Clean build dir
rm -rf "$BUILD_DIR"
# Clean other build artifacts
rm -rf "$SCRIPT_DIR/dist"
rm -rf "$SCRIPT_DIR/.quasar"
rm -rf "$TAURI_DIR/target"
# Restore logs
mkdir -p "$BUILD_DIR"
if [ -d "$SCRIPT_DIR/.logs_backup" ]; then
mv "$SCRIPT_DIR/.logs_backup" "$LOGS_DIR"
fi
if [ -d "$SCRIPT_DIR/.console_backup" ]; then
mv "$SCRIPT_DIR/.console_backup" "$CONSOLE_DIR"
fi
success "Build folder cleaned!"
exit 0
fi
# Require a build mode if not running a command
if [ -z "$BUILD_MODE" ]; then
error "No build mode specified. Use -dev or -prod. See --help for usage."
fi
# Create log directories
mkdir -p "$LOGS_DIR"
mkdir -p "$CONSOLE_DIR"
# Set up logging
BUILD_LOG="$LOGS_DIR/build_${BUILD_MODE}_$TIMESTAMP.log"
CONSOLE_LOG="$CONSOLE_DIR/console_$TIMESTAMP.log"
# Create symlinks to latest logs
ln -sf "build_${BUILD_MODE}_$TIMESTAMP.log" "$LOGS_DIR/build_latest.log"
ln -sf "console_$TIMESTAMP.log" "$CONSOLE_DIR/console_latest.log"
# Logging function that writes to both console and file
log_both() {
echo -e "${BLUE}[Build]${NC} $1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$BUILD_LOG"
}
# Set paths based on build mode
if [ "$BUILD_MODE" = "prod" ]; then
APP_BUNDLE="$PROD_BUILD_DIR/$APP_NAME.app"
INSTALL_BUNDLE="$PROD_INSTALL_DIR/$APP_NAME.app"
BUNDLE_ID="$BUNDLE_ID_BASE"
else
APP_BUNDLE="$DEV_BUILD_DIR/$APP_NAME.app"
INSTALL_BUNDLE=""
BUNDLE_ID="${BUNDLE_ID_BASE}.dev"
fi
log_both "=========================================="
log_both "Building $APP_NAME ($BUILD_MODE mode)"
log_both "=========================================="
log_both "Build log: $BUILD_LOG"
log_both "Console log: $CONSOLE_LOG"
# Create build directories
mkdir -p "$DEV_BUILD_DIR"
mkdir -p "$PROD_BUILD_DIR"
# Kill existing instances before building
log_both "Stopping existing instances..."
pkill -f "$DEV_BUILD_DIR/$APP_NAME.app/Contents/MacOS" 2>/dev/null || true
pkill -f "$PROD_BUILD_DIR/$APP_NAME.app/Contents/MacOS" 2>/dev/null || true
pkill -f "$PROD_INSTALL_DIR/$APP_NAME.app/Contents/MacOS" 2>/dev/null || true
sleep 1
# Check dependencies
command -v node >/dev/null 2>&1 || error "Node.js is required but not installed"
command -v cargo >/dev/null 2>&1 || error "Rust/Cargo is required but not installed"
# Step 1: Install npm dependencies if needed
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
log_both "Installing npm dependencies..."
npm install 2>&1 | tee -a "$BUILD_LOG"
fi
# Step 2: Build with Tauri (builds frontend + Rust + creates bundle)
log_both "Building with Tauri (this embeds frontend into binary)..."
cd "$SCRIPT_DIR"
if [ "$SHOW_INSTALL" = true ]; then
# No --ci flag: opens Finder window for drag-to-Applications
npx tauri build 2>&1 | tee -a "$BUILD_LOG"
else
# --ci flag: suppress Finder window
npx tauri build --ci 2>&1 | tee -a "$BUILD_LOG"
fi
# Find the generated app bundle
TAURI_BUNDLE_DIR="$TAURI_DIR/target/release/bundle/macos"
TAURI_APP_BUNDLE="$TAURI_BUNDLE_DIR/$APP_NAME.app"
if [ ! -d "$TAURI_APP_BUNDLE" ]; then
error "Tauri build failed - app bundle not found at $TAURI_APP_BUNDLE"
fi
# Step 3: Copy the Tauri-built bundle to our build directory
log_both "Copying app bundle to $APP_BUNDLE..."
rm -rf "$APP_BUNDLE"
cp -R "$TAURI_APP_BUNDLE" "$APP_BUNDLE"
# Step 4: Update Info.plist with our bundle ID (dev vs prod)
log_both "Updating Info.plist with bundle ID: $BUNDLE_ID..."
if [ "$BUILD_MODE" = "dev" ]; then
DISPLAY_NAME="$APP_NAME (Dev)"
else
DISPLAY_NAME="$APP_NAME"
fi
# Use PlistBuddy to update the plist
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$APP_BUNDLE/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $DISPLAY_NAME" "$APP_BUNDLE/Contents/Info.plist" 2>/dev/null || \
/usr/libexec/PlistBuddy -c "Add :CFBundleDisplayName string $DISPLAY_NAME" "$APP_BUNDLE/Contents/Info.plist"
# Step 5: For prod, also install to ~/Applications
if [ "$BUILD_MODE" = "prod" ] && [ -n "$INSTALL_BUNDLE" ]; then
log_both "Installing to $INSTALL_BUNDLE..."
pkill -f "$INSTALL_BUNDLE/Contents/MacOS" 2>/dev/null || true
sleep 1
rm -rf "$INSTALL_BUNDLE"
mkdir -p "$PROD_INSTALL_DIR"
cp -R "$APP_BUNDLE" "$INSTALL_BUNDLE"
touch "$INSTALL_BUNDLE"
fi
# Touch the app to update Finder cache
touch "$APP_BUNDLE"
# Build complete
log_both "=========================================="
success "Build Complete!"
log_both "=========================================="
echo ""
echo " Build folder: $BUILD_DIR"
echo " App bundle: $APP_BUNDLE"
echo " Mode: $BUILD_MODE"
echo " Bundle ID: $BUNDLE_ID"
if [ "$BUILD_MODE" = "prod" ] && [ -n "$INSTALL_BUNDLE" ]; then
echo " Installed to: $INSTALL_BUNDLE"
fi
echo ""
echo " Build log: $BUILD_LOG"
echo " Console log: $CONSOLE_LOG"
echo ""
# Open the app and capture console output
if [ "$OPEN_APP" = true ]; then
log_both "Opening app (console output will be logged)..."
# Run the app and capture output to console log
"$APP_BUNDLE/Contents/MacOS/$BINARY_NAME" 2>&1 | while IFS= read -r line; do
echo "$line"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $line" >> "$CONSOLE_LOG"
done &
echo " App is running. Console output logged to:"
echo " $CONSOLE_LOG"
echo ""
echo " View logs with: ./build.sh logs"
else
echo " To run: open \"$APP_BUNDLE\""
echo " Or with logging: \"$APP_BUNDLE/Contents/MacOS/$BINARY_NAME\" 2>&1 | tee -a \"$CONSOLE_LOG\""
fi
echo ""

623
templates/claude.md Normal file
View File

@ -0,0 +1,623 @@
# Project Guidelines
## Protecting This File
**IMPORTANT**: This file (claude.md) is gitignored and can be lost during branch operations!
Git doesn't protect untracked/ignored files during `git checkout` or branch switches. Before major git operations:
```bash
cp claude.md claude.md.backup
```
## Coding Best Practices (CRITICAL)
**Read `coding-best-practices.md` before writing any code.** It contains personal coding conventions that apply to every project — naming rules, file/function size limits, folder nesting patterns, persistence rules, and language-specific structure guides. Those rules override defaults and must be followed.
### File Header Check Rule
**Every time you open a file to work on it, check the file header first.** Look for outstanding `@todo` items, unresolved `@issues`, or outdated `@status` notes. If any are completed or resolved, clean them up before doing other work. If a TODO is relevant to the current task, address it. This ensures nothing falls through the cracks.
## Project Structure (CRITICAL)
This project follows a standardized structure for future modularity. The long-term goal is to enable drag-and-drop of components between projects. **This structure must be followed.**
```
ProjectRoot/ # git root
├── claude.md # (gitignored) AI assistant guidelines
├── coding-best-practices.md # Coding conventions (read before Phase 1)
├── build_spec.md # (gitignored) Full project specification
├── build_plan.md # (gitignored) Phased task checklist
├── .gitignore
├── docs/ # Public-facing documentation (parsed to HTML for website)
│ ├── README.md # Docs index with table of contents
│ ├── getting-started.md
│ ├── architecture.md
│ ├── development.md
│ ├── deployment.md
│ └── feature-*.md # Feature docs (added per phase)
├── InternalDocs/ # Internal development documentation (not public)
│ └── ... # Design decisions, technical debt, internal API docs
└── app/ # ALL project code lives here
├── src/
│ ├── assets/ # Static assets
│ ├── boot/ # Quasar boot files (nhost init, etc.)
│ ├── components/ # Vue components
│ ├── composables/ # Vue composables
│ ├── layouts/ # Page layouts
│ ├── pages/ # Route pages
│ ├── router/ # Vue Router config
│ ├── stores/ # Pinia stores
│ ├── services/ # API services
│ └── types/ # TypeScript types
├── nhost/ # Nhost configuration
│ ├── functions/ # Serverless functions
│ ├── migrations/ # Database migrations
│ └── metadata/ # Hasura metadata
├── public/ # Public static files
├── docker-compose.yml
├── .env # Dev environment (gitignored)
└── .env.prod # Prod environment (gitignored)
```
## Terminology
- **"build spec"** or **"spec"** refers to `build_spec.md` - the full project description
- **"build plan"** or **"plan"** refers to `build_plan.md` - the phased task checklist
## Onboarding Questions (Ask at Project Start)
At the start of every new project conversation, gather these details before writing any code:
1. **Brand colors & fonts** - Primary, secondary, accent colors. Typography preferences. Any existing brand guidelines or color palette?
2. **App personality & tone** - Formal or casual? Dark or light default theme? Dense or spacious layout? Any design inspiration or reference apps?
3. **Target platform** - Desktop-first, mobile-first, or responsive? Specific OS targets (macOS, Windows, Linux)? Minimum screen sizes?
4. **Auth requirements** - Login method (email/password, OAuth, magic link)? User roles and permissions? Public or private app?
Don't assume defaults for any of these. Ask, get answers, then document them in the Project-Specific Configuration section at the bottom of this file.
## Tech Stack
- **Frontend**: Quasar Framework (Vue 3) - SPA mode
- **Desktop** (optional): Tauri (Rust) - Cross-platform builds (macOS, Windows, Linux)
- **Backend**: Nhost (self-hosted via Docker)
- PostgreSQL - Database
- Hasura - GraphQL API
- Auth - Authentication service
- Storage - File storage
- Functions - Serverless functions (Node.js)
- **Containerization**: Docker / Docker Compose
### Tauri Desktop Apps
When building desktop apps with Tauri:
- Add `src-tauri/` folder inside `app/`
- Use the build-and-run approach (no hot reload) for consistent dev/prod builds
- Separate bundle IDs for dev and prod (can run both simultaneously)
## Testing Changes (CRITICAL)
**After every code editing session, you MUST rebuild the app so the user can test your changes.**
```bash
cd app
./build.sh -dev -open
```
### Why This Matters
- This uses a **build-and-run** approach, not hot reload
- Your code changes are NOT visible until you rebuild
- Always rebuild before asking the user to test
- If the user reports issues, check the logs first: `./build.sh logs`
## Build Script
**Always use `build.sh` for building the app.** This ensures consistent builds and logging for collaborative debugging.
```bash
cd app
./build.sh -dev -open # Build dev and open app
./build.sh -prod # Build prod to build/prod/
./build.sh -install # Build prod and open Finder for drag-to-Applications
./build.sh logs # View latest console output (last 100 lines)
./build.sh clean # Clean build artifacts (preserves logs)
./build.sh clean-logs # Remove all logs
```
### Commands
| Command | Description |
|---------|-------------|
| `-dev` | Development build. Bundle ID: `com.[app].dev` |
| `-prod` | Production build. Builds to `build/prod/`. |
| `-install` | Build prod and open Finder for drag-to-Applications. |
| `-open` | Open the app after building (use with `-dev` or `-prod`) |
| `logs` | Show last 100 lines of console output |
| `clean` | Remove build artifacts (preserves logs) |
| `clean-logs` | Remove all log files |
### Why Build-and-Run (No Hot Reload)
This project uses a **build-and-run** approach instead of hot reload:
| Aspect | Hot Reload | Build-and-Run |
|--------|------------|---------------|
| Speed | Instant updates | Full rebuild required |
| Dev/Prod parity | Different environments | Identical to production |
| Debugging | Dev-only issues possible | What you test = what ships |
| Logging | Console only | Full logs to files |
The tradeoff is slower iteration, but you always test exactly what will ship.
### What the Build Does
1. **Build with Tauri** - `npx tauri build` does all of the following:
- Build frontend (`npm run build` → `dist/spa/`)
- Build Rust binary with embedded frontend
- Create .app bundle in `src-tauri/target/release/bundle/macos/`
2. **Copy to build folder** - Copies bundle to `build/dev/` or `build/prod/`
3. **Update Info.plist** - Sets correct bundle ID for dev/prod
4. **Install (prod only)** - Copies to `~/Applications`
**Important**: Using `npx tauri build` ensures the frontend is properly embedded in the binary. Manual `cargo build --release` does NOT embed the frontend.
### Build Output
Builds are organized in separate folders:
```
app/build/
├── dev/
│ └── AppName.app # Dev build (bundle ID: .dev)
├── prod/
│ └── AppName.app # Prod build
├── logs/
│ ├── build_dev_*.log # Build output logs
│ └── build_latest.log # Symlink to latest
└── console/
├── console_*.log # App runtime console output
└── console_latest.log # Symlink to latest
```
Dev and prod have different bundle IDs, so both can run simultaneously.
### Logging
All build output and app console output is logged:
- **Build logs**: `build/logs/build_latest.log` - compilation output, errors
- **Console logs**: `build/console/console_latest.log` - runtime `console.log()`, errors
When debugging, run `./build.sh logs` to see the latest console output.
### Console Logging Setup (Tauri)
For frontend `console.log()` to appear in the logs, you need:
1. **Boot file** - Copy `templates/scripts/console-logger.ts` to `app/src/boot/`
2. **Register boot** - Add `'console-logger'` to `boot` array in `quasar.config.js`
3. **Rust command** - Add `log_to_stdout` command to `src-tauri/src/commands.rs`
4. **Cargo dep** - Add `chrono = "0.4"` to `Cargo.toml`
The console-logger.ts template includes the Rust code you need to add.
**How it works:**
```
console.log() → console-logger.ts → invoke('log_to_stdout') → Rust println!() → stdout → build.sh logs
```
### When to Clean
Run `./build.sh clean` when:
- Switching between major dependency versions
- Build errors reference stale/cached files
- Rust compilation issues after Tauri config changes
- Before creating a fresh production build
## Node.js Environment
**Node.js version**: Always match Debian stable (currently Node.js 18.x for Debian 12 Bookworm)
Check current Debian stable Node version: https://packages.debian.org/stable/nodejs
**Package manager**: Use npm. Commit lock files for reproducible builds.
Rules:
- Always commit `package-lock.json` - required for deterministic installs
- Never ignore lock files (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`)
- Use `npm install` to install dependencies
- Use `npm run <script>` to run package scripts
## Communication Rules
**Always explain before acting.** Before running any command, SSH, database query, or making changes:
- Give a short, concise explanation of WHAT you're about to do and WHY
- Wait for approval on anything that modifies production data or server state
- Don't chain multiple server commands without explaining each step
## Database Rules (CRITICAL)
**NEVER make direct database changes via Hasura MCP or Hasura Console.**
All database changes MUST be done through migrations so they:
1. Are tracked in version control
2. Persist across restarts (`nhost down` / `nhost up`)
3. Can be reproduced in production
### Hasura MCP Usage
- **READ-ONLY**: Use Hasura MCP only to inspect schema, check what tables exist, verify tracking status
- **NO MUTATIONS**: Never use it to create tables, add columns, track tables, or modify permissions
- **MIGRATIONS ONLY**: All schema changes go in `app/nhost/migrations/default/`
- **METADATA ONLY**: All Hasura config (tracking, permissions, relationships) go in `app/nhost/metadata/`
### How to Make Database Changes
1. Write SQL migration in `app/nhost/migrations/default/<N>_<name>/up.sql` (N = next version number)
2. Write rollback in `app/nhost/migrations/default/<N>_<name>/down.sql`
3. Add Hasura metadata YAML in `app/nhost/metadata/databases/default/tables/`
4. Restart dev environment to apply migrations
5. Commit and push
**Migration folder naming:**
```
app/nhost/migrations/default/
├── 1_init_schema/
├── 2_add_users/
├── 3_add_settings/
└── ...next version number...
```
### Hasura Role Permissions (CRITICAL)
**NEVER define permissions for the `admin` role in Hasura metadata.**
Hasura reserves `admin` as a special role with **full unrestricted access**. Defining explicit permissions for `admin` causes metadata errors:
```
"cannot define permission for admin role"
```
**Correct approach:**
- Define permissions only for `user`, `public`, and custom roles
- Users with `X-Hasura-Role: admin` automatically bypass all permission checks
**Example - WRONG:**
```yaml
select_permissions:
- role: admin # DON'T DO THIS
permission:
columns: [id, name]
filter: {}
```
**Example - CORRECT:**
```yaml
select_permissions:
- role: user # Only define for non-admin roles
permission:
columns: [id, name]
filter: { created_by: { _eq: X-Hasura-User-Id } }
```
## Development Approach
**Integrate-as-you-go** - Connect real backend services as you build. Don't build elaborate mock systems. The Docker infrastructure provides:
- Database & Hasura - Real tables and GraphQL
- Nhost Auth - Real authentication
- Real data from the start
## Persistence Rules (CRITICAL)
**All persistence requires an explicit user action. No exceptions.**
Never auto-save. Never sync state in the background. Never write to the database on a timer or watcher. Every single write to a database, file, or storage must be traceable to a deliberate user action.
### The Rule
Data is only persisted when the user deliberately triggers it — clicking "Save", submitting a form, confirming a dialog, toggling a setting, etc.
### WRONG — Never Do This
- Auto-saving form fields as the user types
- Syncing component state to the database on mount/unmount
- Watchers that write to storage when reactive data changes
- Debounced save on input change
- Persisting UI state automatically (sidebar position, tab selection, scroll position)
- Writing to the database during navigation or route changes
- Background sync intervals or polling that writes data
### RIGHT — Always Do This
- User clicks "Save" → write to database
- User submits a form → persist the data
- User explicitly toggles a setting → save that setting
- User clicks "Delete" → confirm, then remove from database
- User drags an item to reorder → save on drop (explicit action)
### Why This Matters
Auto-save creates unpredictable behavior, race conditions, and persists data the user didn't intend to save. Every write should be traceable to a conscious user decision. If you can't point to the button or action the user took to trigger the save, it shouldn't be saving.
### Implementation Pattern
```
User Action → Service Call → Database Write → Confirm Success to User
```
1. User triggers action (click, submit, confirm)
2. Call the appropriate service/API
3. Write to database
4. Show success feedback (Quasar Notify or UI update)
5. If it fails, show an error — don't silently retry
## Code Quality
### TypeScript
- Use TypeScript strict mode
- Define interfaces/types for all data structures in `src/types/`
- No `any` types unless absolutely unavoidable — use `unknown` and narrow instead
- Type all function parameters and return values
### Vue / Quasar Conventions
- Use Composition API with `<script setup lang="ts">` for all components
- One component per file, named in PascalCase (e.g., `UserProfile.vue`)
- Composables go in `src/composables/`, prefixed with `use` (e.g., `useAuth.ts`)
- Keep components focused — if it's doing too much, split it
- Props down, events up — don't mutate props, emit events to parent
### Naming
- **Files**: PascalCase for components (`UserCard.vue`), camelCase for everything else (`userService.ts`)
- **Variables/functions**: camelCase
- **Constants**: UPPER_SNAKE_CASE for true constants, camelCase for derived/computed values
- **Types/Interfaces**: PascalCase, no `I` prefix (use `User` not `IUser`)
- **CSS classes**: kebab-case
### General
- No magic numbers or strings — use named constants
- Keep functions short and single-purpose
- Colocate related code — don't scatter logic across files unnecessarily
- Delete dead code, don't comment it out
## Error Handling
### User-Facing Errors
- Always show human-readable messages — never expose raw error objects, stack traces, or technical details to the user
- Use Quasar's `Notify` plugin for transient feedback (success, warning, error)
- Use inline error messages for form validation
### Technical Errors
- Log technical details to console for debugging (`console.error` with context)
- Include the operation that failed and relevant IDs in log messages
- Never swallow errors silently — if you catch it, either handle it or log it
### Pattern
```typescript
try {
const result = await userService.updateProfile(data)
Notify.create({ type: 'positive', message: 'Profile updated' })
} catch (error) {
console.error('Failed to update profile:', error)
Notify.create({ type: 'negative', message: 'Could not update profile. Try again.' })
}
```
### Where to Catch
- Catch at **service boundaries** (API calls, database operations), not deep in utility functions
- Let errors propagate up to where they can be meaningfully handled
- Don't wrap every line in try/catch — catch at the level where you can show the user feedback
## Documentation
### docs/ vs InternalDocs/
This project has two documentation directories with different audiences:
| Directory | Audience | Purpose | Parsed for website? |
|-----------|----------|---------|---------------------|
| `docs/` | Public | User-facing app documentation — getting started, features, architecture | Yes |
| `InternalDocs/` | Developers | Internal dev context — design decisions, technical debt, internal API contracts, Claude context | No |
**`docs/`** contains ONLY what the public needs. It gets parsed into HTML and served on the app website. No internal implementation details, no dev-only context.
**`InternalDocs/`** contains everything internal — design rationale, known technical debt, internal API documentation, integration notes, debugging guides, anything that helps the development team but doesn't belong in public docs.
Both directories are committed to git.
### Phase Documentation
**Every phase ends with documentation.** After completing a feature or phase:
1. **Create feature docs** - Copy `docs/TEMPLATE-feature.md` to `docs/feature-[name].md`
2. **Update the index** - Add links to `docs/README.md` table of contents
3. **Update core docs** - Keep `architecture.md`, `development.md` current
4. **Update internal docs** - Add design decisions, technical debt notes, or internal API docs to `InternalDocs/` as needed
### docs/ Structure
| File | Purpose |
|------|---------|
| `docs/README.md` | Index with table of contents |
| `docs/getting-started.md` | Installation & setup |
| `docs/architecture.md` | Tech stack & design decisions |
| `docs/development.md` | Development workflow |
| `docs/deployment.md` | Build & deployment |
| `docs/feature-*.md` | Feature-specific documentation |
### Naming Convention
- `feature-[name].md` - Feature docs (e.g., `feature-authentication.md`)
- `component-[name].md` - Component docs
- `integration-[name].md` - Integration docs
- `guide-[topic].md` - How-to guides
## Commit Rules
- **Never mention Claude, AI, or "Generated with Claude Code" in commits**
- All commits are authored by the developer - no co-author tags
- Write commit messages in first person as the developer
- Keep commit messages concise and descriptive of the changes
## Branch Strategy
- **main** - Production branch
- **test** - Staging/testing branch
**Workflow:**
1. Develop and test locally
2. Push to `test` branch for staging
3. Verify on staging
4. Merge `test` → `main` for production
## Environment Variables
```env
# Nhost
NHOST_ADMIN_SECRET=
# PostgreSQL
POSTGRES_PASSWORD=
# JWT
JWT_SECRET=
# App
APP_URL=
```
---
# Project-Specific Configuration
<!-- Everything below this line is customized per project -->
## Project Name
[PROJECT_NAME]
## Description
[Brief description from build_spec.md]
## Database Schema
[Tables and relationships]
## URLs
| Service | Local | Production |
|---------|-------|------------|
| App | http://localhost:[dynamic] | |
| GraphQL | http://localhost:8080 | |
**Note**: For Tauri desktop apps, use dynamic port allocation. The dev server will find the first available port.
## MCP Servers
[Configure in .mcp.json if needed]
## Servers
[Production server details if applicable]
## Dockerized Frontend for Production
When deploying a fully dockerized project, add a frontend service to docker-compose:
### 1. Create `app/Dockerfile.frontend`
```dockerfile
# Build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
### 2. Create `app/nginx.conf`
```nginx
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
### 3. Add frontend service to `docker-compose.yml`
All Traefik hostnames use hardcoded localhost values. TeamIDE's proxy rewrites the Host header to localhost, so this works both locally and in production.
```yaml
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
depends_on:
graphql:
condition: service_healthy
auth:
condition: service_healthy
labels:
traefik.enable: "true"
traefik.http.routers.frontend.rule: Host(`localhost`)
traefik.http.routers.frontend.entrypoints: web
traefik.http.services.frontend.loadbalancer.server.port: "80"
extra_hosts:
- host.docker.internal:host-gateway
- localhost:host-gateway
- auth.localhost:host-gateway
- hasura.localhost:host-gateway
- storage.localhost:host-gateway
restart: always
```
### 4. Traefik hostname pattern
All services use hardcoded localhost hostnames:
- Frontend: `Host(\`localhost\`)`
- Auth: `Host(\`auth.localhost\`)`
- Hasura: `Host(\`hasura.localhost\`)`
- Storage: `Host(\`storage.localhost\`)`
- Functions: `Host(\`functions.localhost\`)`
## TeamIDE Hosting
This project is deployed via TeamIDE.
- **Workspace ID**: `your-workspace-id-here`
- Use `teamide_deploy` to deploy changes
- Use `teamide_set_env_vars` to configure environment variables
- Use `teamide_get_logs` to check build status
To get your workspace ID, run:
```bash
teamide workspace list
```

View File

@ -0,0 +1,702 @@
# Coding Best Practices
**Read this file at the start of any coding phase.** These are personal coding conventions that apply to every project, every language, every framework. They override defaults and must be followed unless explicitly told otherwise.
---
## Philosophy
Code is navigated spatially. The folder tree is a mental map — every directory, file, and function name is a landmark. When the structure is clean and names are descriptive, you can find what you need without searching. When it's not, you're lost in your own codebase.
These rules exist to keep code **navigable, scannable, and small enough to hold in your head**.
---
## Universal Rules
These apply to ALL code in ALL languages. No exceptions.
### 1. Descriptive Names — Everything
Every name must tell you what it is at a glance. Files, folders, functions, variables, types, classes, routes, components, database tables, columns — everything.
**The test:** Would you know what this is from the file tree alone, without opening it?
| Bad | Good |
|-----|------|
| `utils.ts` | `dateFormatUtils.ts` |
| `helpers.ts` | `pricingCalculationHelpers.ts` |
| `data.ts` | `userSessionData.ts` |
| `handler()` | `handleFileUploadComplete()` |
| `process()` | `processIncomingWebhookPayload()` |
| `calc()` | `calculateMonthlySubscriptionCost()` |
| `Item` | `InventoryLineItem` |
| `manage()` | `manageConnectionPool()` |
| `Service` | `NotificationDeliveryService` |
| `tmp` | `pendingUploadBuffer` |
Prefer longer, descriptive names over short, ambiguous ones. Abbreviations are acceptable only when universally understood (`id`, `url`, `http`, `db`, `api`, `auth`).
### 2. Small Functions — ~100 Lines of Code Max
The ~100-line limit applies to the **function body only** — the executable logic. Documentation comments above the function do NOT count toward this limit.
- If a function body is growing past ~100 lines, look for logical extraction points
- Each function should do one thing and do it well
- Aim for concise, precise code — fewer lines that do more, not sprawling logic
- Use one blank line to separate logical chunks within a function for readability — but no more than one
- Don't add unnecessary blank lines just for spacing — simplify the logic instead
### 3. Small Files — Under 1,000 Lines of Code
The 1,000-line limit applies to **code only**. Documentation comments, file headers, and inline documentation do NOT count toward this limit. This limit exists to enforce efficient, concise code — not to punish well-documented code.
- Prefer more files with fewer functions each
- Every function in a file must logically belong there — don't put camera rotation functions in a text editor file
- Split where it makes semantic sense, not just to hit a number
- The folder nesting structure exists to support these smaller units
### 4. Library-First Design
Write all code as if every function could be extracted into a standalone library. The end goal is maximum reusability across projects.
**Principles:**
- Functions should be self-contained with minimal external dependencies
- Clean, well-defined interfaces — inputs and outputs should be obvious
- Avoid hard-coded project-specific values inside reusable logic — pass them as parameters
- Group related functions that could form a library into their own files/modules
- In any language that supports libraries (C, Rust, JS/TS, Swift, etc.), structure code so extraction into a library requires moving files, not rewriting them
**For C specifically:** This is the primary target. Every utility, algorithm, data structure, and system abstraction should be written as a reusable library from day one. Headers define the public API contract, source files contain the implementation. A well-written C library can be linked into any project.
### 5. No Auto-Save / No Background Persistence
**Every write to a database, file, or storage must trace back to a deliberate user action.** No exceptions unless explicitly requested.
Never implement:
- Auto-saving as the user types
- Debounced save on input change
- Watchers that persist reactive state changes
- Background sync intervals
- Persisting UI state automatically (sidebar position, tab selection, scroll)
- Writing to storage on mount/unmount/navigation
Always require:
- User clicks "Save" → write
- User submits form → persist
- User explicitly toggles setting → save
- User confirms action → execute
**Pattern:** `User Action → Service Call → Write → Confirm Success`
### 6. Folder Nesting as Navigation
Directories are not just organization — they're how you navigate. The tree structure should let you drill down to the right file by following a logical path, not by guessing or searching.
**Principles:**
- Group by **domain/feature**, not by file type (prefer `auth/authService.ts` over `services/auth.ts` when the module is complex enough)
- Each directory should have a clear, single purpose
- Nesting depth should match conceptual depth — don't nest just for nesting, don't flatten when there's real hierarchy
- A directory with 1 file is usually wrong — either the file belongs in the parent or more files belong in the directory
### 7. Error Transparency
- Never swallow errors silently
- Catch at service boundaries where you can show user feedback
- Log technical details (`console.error` with context)
- Show human-readable messages to users — never raw errors, stack traces, or technical details
- If you catch it, either handle it meaningfully or log it. Never empty catch blocks.
### 8. Delete Dead Code
Don't comment out code. Don't leave unused functions, imports, or variables. If it's not being used, delete it. Git has the history if you ever need it back.
---
## Inline Code Documentation
Every source file carries its own documentation. This gives both the developer and Claude immediate context the moment a file is opened — no hunting through separate docs.
### File Header — Always Present
Every source file MUST have a header block at the top with three sections: **status**, **issues**, and **todo**. All three sections are always present, even when empty. The empty slots serve as a reminder that documentation can go there — if the sections aren't present, they get forgotten.
**Template (JS/TS/Vue):**
```typescript
/**
* @file DescriptiveFileName
* @description What this file is for and what it does (1-3 concise lines)
*
* @status None
* @issues None
* @todo None
*/
```
**Template (Rust):**
```rust
//! # DescriptiveModuleName
//! What this module is for and what it does (1-3 concise lines)
//!
//! ## Status
//! None
//!
//! ## Issues
//! None
//!
//! ## Todo
//! None
```
**Template (C — header files):**
```c
/**
* @file descriptive_name.h
* @brief What this header defines and its purpose (1-3 concise lines)
*
* @status None
* @issues None
* @todo None
*/
```
**Template (C — source files):**
```c
/**
* @file descriptive_name.c
* @brief Implementation of [what this file implements]
*
* @status None
* @issues None
* @todo None
*/
```
**Template (Swift):**
```swift
/// # DescriptiveFileName
/// What this file is for and what it does (1-3 concise lines)
///
/// ## Status
/// None
///
/// ## Issues
/// None
///
/// ## Todo
/// None
```
**When there IS something to document:**
```typescript
/**
* @file AvatarUploadService
* @description Handles user avatar upload, validation, and storage.
* Supports JPEG, PNG, and WebP formats up to 10MB.
*
* @status Avatar upload works but no progress indicator yet
* @issues Timeout on large files (>10MB) — needs chunked upload
* @todo
* - [ ] Add upload progress bar (blocked on chunked upload)
* - [ ] Validate image dimensions before upload
*/
```
**What each section means:**
| Section | Purpose | Example |
|---------|---------|---------|
| `@status` | Current state of the code — what works, what's partial | "Core CRUD works, batch operations not yet implemented" |
| `@issues` | Known bugs, performance problems, fragile areas | "Race condition when two users edit simultaneously" |
| `@todo` | Incomplete work left during a session — breadcrumbs | "- [ ] Add input validation for email field" |
### File Header Cleanup Rule
**When working in any file, always check the header first.** If there are completed TODOs, resolved issues, or outdated status notes — clean them up. Reset sections to "None" when everything is addressed. This prevents documentation rot.
### File-Level TODOs Are Breadcrumbs
The `@todo` section in file headers is NOT the project plan. The real task tracking lives in plan files and `.teamide/tasks/`. File-level TODOs exist for the "mid-session, had to step away" situation:
- You're deep in a function, a priority comes in — drop a TODO so you don't lose your place
- You notice something needs doing but it's not the current task — note it so it doesn't get forgotten
- If you come back to that file a month later, the TODO catches what fell through the cracks
**Cleanup context:** When a file-level TODO is completed, remove it from the header. When all TODOs are done, reset to "None". If a TODO turns into a real task, move it to the project plan and remove it from the header.
### Function Documentation
Document every public/exported function. Internal helper functions need docs only when the logic isn't obvious from the name and signature.
**JS/TS pattern:**
```typescript
/**
* Calculates the prorated subscription cost for a partial billing period.
* Uses daily rate × remaining days, rounded to 2 decimal places.
*
* @param planMonthlyRate - The full monthly rate in dollars
* @param startDate - When the subscription started (mid-cycle)
* @param billingCycleEnd - End date of the current billing cycle
* @returns Prorated cost in dollars (e.g., 14.52)
*/
function calculateProratedSubscriptionCost(
planMonthlyRate: number,
startDate: Date,
billingCycleEnd: Date
): number {
// function body...
}
```
**C header pattern (API contract):**
```c
/**
* Allocates a block from the thread-local memory pool.
* Falls back to system malloc if pool is exhausted.
*
* @param size Bytes to allocate (rounded up to pool alignment)
* @return Pointer to allocated block, or NULL on failure
*
* Thread-safe: yes (per-thread pools, no locks needed)
* Reentrant: no (modifies pool state)
*/
void *pool_alloc(size_t size);
```
**Rust pattern:**
```rust
/// Calculates the prorated subscription cost for a partial billing period.
/// Uses daily rate × remaining days, rounded to 2 decimal places.
///
/// # Arguments
/// * `plan_monthly_rate` - The full monthly rate in dollars
/// * `start_date` - When the subscription started (mid-cycle)
/// * `billing_cycle_end` - End date of the current billing cycle
///
/// # Returns
/// Prorated cost in dollars (e.g., 14.52)
fn calculate_prorated_subscription_cost(
plan_monthly_rate: f64,
start_date: NaiveDate,
billing_cycle_end: NaiveDate,
) -> f64 {
// function body...
}
```
### Inline Comments
Use inline comments to explain **why**, not **what**. The code shows what it does — comments explain the reasoning that isn't obvious.
```typescript
// Offset by 1 because the API uses 1-based page indexing
const apiPage = displayPage + 1;
// Must check auth before loading data — the API returns 200 with empty
// results for unauthorized users instead of 401
await verifyAuthStatus();
const results = await loadDashboardData();
```
Don't write comments that just restate the code:
```typescript
// BAD — restates the code
const total = price * quantity; // multiply price by quantity
// GOOD — explains the business rule
const total = price * quantity; // pre-tax total, tax applied at checkout
```
---
## Folder Structure Patterns by Language
The core principle is the same everywhere: **categorical nesting by domain**, with descriptive names, keeping files small and focused. Here's how that maps to each language ecosystem.
### JavaScript / TypeScript (Vue, Quasar, Node)
```
src/
├── modules/ # Feature modules (self-contained)
│ ├── auth/
│ │ ├── index.ts # Module entry, re-exports
│ │ ├── store.ts # Pinia store (composition API)
│ │ ├── api.ts # API calls for this module
│ │ ├── types.ts # Types/interfaces for this module
│ │ ├── AuthLogin.vue # Components named descriptively
│ │ ├── AuthRegister.vue
│ │ └── components/ # Sub-components if needed
│ │ └── AuthFormField.vue
│ └── dashboard/
│ ├── index.ts
│ ├── store.ts
│ ├── api.ts
│ └── DashboardOverview.vue
├── services/ # Shared services (API client, DB, etc.)
│ ├── apiClient.ts # Centralized fetch wrapper
│ ├── db/
│ │ ├── client.ts
│ │ └── tables/
│ │ ├── userSettings.ts
│ │ └── sessionHistory.ts
│ └── fileWatcher.ts
├── stores/ # Global stores (app-wide state)
│ └── globalAppState.ts
├── components/ # Shared components (used across modules)
│ ├── AppHeader.vue
│ └── ConfirmDialog.vue
├── composables/ # Shared composables
│ └── useWindowResize.ts
├── types/ # Global types
│ └── appTypes.ts
├── layouts/ # Page layouts
├── pages/ # Route pages
├── router/ # Vue Router config
├── boot/ # Startup/init files
├── css/ # Global styles
└── utils/ # Platform detection, formatters
└── platformDetection.ts
```
**Key conventions:**
- Each feature module has `index.ts`, `store.ts`, `api.ts`, `types.ts`
- Components are PascalCase: `UserProfileCard.vue`
- Everything else is camelCase: `apiClient.ts`, `useAuth.ts`
- Path aliases: `@/` for `src/`, `@modules/`, `@components/`, `@stores/`
### Rust
Apply the same categorical nesting using Rust's module system:
```
src/
├── main.rs # Entry point, minimal — just init and run
├── app.rs # Application setup, config, state
├── lib.rs # Library entry (if crate is also a lib)
├── commands/ # Tauri commands or CLI handlers
│ ├── mod.rs # Re-exports all command modules
│ ├── file_operations.rs # File read/write/watch commands
│ ├── window_management.rs # Window creation/positioning
│ └── backend_lifecycle.rs # Start/stop/health-check backend
├── services/ # Business logic, reusable across commands
│ ├── mod.rs
│ ├── process_manager.rs # Spawn/kill/monitor child processes
│ ├── port_allocator.rs # Find available ports
│ └── log_writer.rs # Write structured logs to files
├── models/ # Data structures, enums, types
│ ├── mod.rs
│ ├── app_config.rs # Config structs
│ └── backend_state.rs # Backend process state
├── platform/ # OS-specific code
│ ├── mod.rs # Conditional compilation re-exports
│ ├── macos.rs # macOS-specific (menus, notifications)
│ ├── windows.rs
│ └── linux.rs
└── utils/ # Small, focused helpers
├── mod.rs
└── path_resolver.rs # Resolve app paths, home dir, etc.
```
**Key conventions:**
- `mod.rs` in each directory for re-exports (like `index.ts`)
- snake_case for everything: files, functions, variables, modules
- Descriptive module names: `backend_lifecycle.rs` not `backend.rs`
- Keep `main.rs` thin — delegate to `app.rs` or modules immediately
- Group by responsibility, not by Rust convention alone
- Include `lib.rs` when the crate should be usable as a library
### C
Follow categorical nesting for headers, with source files mirroring the structure. Every module should be structured for library extraction from day one.
```
include/ # Public headers (categorical nesting)
├── core/ # Core functionality
│ ├── memory_allocator.h
│ ├── string_operations.h
│ └── error_codes.h
├── arch/ # Architecture-specific
│ ├── arm64.h
│ ├── x86_64.h
│ └── riscv32.h
├── os/ # OS-specific abstractions
│ ├── linux_syscalls.h
│ ├── darwin_syscalls.h
│ └── windows_syscalls.h
├── fmt/ # File format parsers
│ ├── elf_parser.h
│ ├── macho_parser.h
│ └── pe_parser.h
└── sys/ # System-level interfaces
└── thread_pool.h
src/ # Implementation files (mirror include/ structure)
├── core/
│ ├── memory_allocator.c
│ ├── string_operations.c
│ └── error_codes.c
├── arch/
│ ├── arm64.c
│ └── x86_64.c
└── os/
├── linux_syscalls.c
└── darwin_syscalls.c
tests/ # Test files (mirror src/ structure)
├── unit/
│ ├── test_memory_allocator.c
│ └── test_string_operations.c
└── integration/
└── test_full_pipeline.c
```
**Key conventions:**
- `include/` and `src/` mirror each other's structure
- Category directories by domain: `core/`, `arch/`, `os/`, `fmt/`, `sys/`
- snake_case for all file names
- Header names describe what's inside: `memory_allocator.h` not `mem.h`
- One header + one source per logical unit
- Headers define the full API contract (see Function Documentation section)
- Each category directory is a potential standalone library
### Swift (macOS / iOS)
```
Sources/
├── App/ # App entry, lifecycle
│ ├── AppMain.swift
│ └── AppDelegate.swift
├── Features/ # Feature modules
│ ├── Camera/
│ │ ├── CameraManager.swift
│ │ ├── CameraPreviewView.swift
│ │ └── CameraPermissions.swift
│ └── Recording/
│ ├── RecordingSession.swift
│ ├── RecordingOverlay.swift
│ └── RecordingStorage.swift
├── Services/ # Shared services
│ ├── FileStorageService.swift
│ └── NotificationService.swift
├── Models/ # Data models
│ ├── RecordingMetadata.swift
│ └── UserPreferences.swift
├── Views/ # Shared UI components
│ ├── PermissionRequestView.swift
│ └── StatusIndicator.swift
├── Extensions/ # Swift extensions
│ └── DateFormattingExtensions.swift
└── Utilities/ # Small helpers
└── DeviceCapabilities.swift
```
**Key conventions:**
- PascalCase for files (Swift convention), but still descriptive
- Feature-based grouping under `Features/`
- `CameraPermissions.swift` not `Permissions.swift` — qualify the name
### General Pattern (Any Language)
When you encounter a new language, apply this structure:
```
src/ (or equivalent)
├── [entry point] # Minimal — just init and delegate
├── features/ or modules/ # Self-contained feature units
│ └── [feature_name]/
│ ├── [entry/index] # Re-exports or module definition
│ ├── [types/models] # Data structures for this feature
│ ├── [api/handlers] # External interface (API calls, commands)
│ └── [service/logic] # Business logic
├── services/ # Shared, cross-cutting services
├── models/ or types/ # Global data structures
├── platform/ # OS/environment-specific code
└── utils/ # Small, focused helpers
```
---
## Code Style Conventions
### Import Organization (JS/TS)
Organize imports in 3 groups, separated by blank lines:
```typescript
// 1. Framework & language imports
import { ref, computed, watch, onMounted } from 'vue';
import { defineStore } from 'pinia';
// 2. Third-party libraries
import { format } from 'date-fns';
// 3. Local imports (project code)
import { useGlobalStore } from '@/stores/globalAppState';
import { request } from '@/services/apiClient';
import type { UserProfile } from './types';
```
### Vue Component Structure
Always this order in single-file components:
```vue
<template>
<!-- Template first — the visual output -->
</template>
<script setup lang="ts">
// Script second — the logic
</script>
<style scoped lang="scss">
// Style last — scoped to this component
</style>
```
Within `<script setup>`:
1. Imports
2. Store references
3. Props & emits
4. Reactive state (`ref`, `reactive`)
5. Computed properties
6. Methods / handlers
7. Watchers
8. Lifecycle hooks (`onMounted`, etc.)
### Pinia Store Pattern
Always use composition API style:
```typescript
export const useFeatureStore = defineStore('feature-name', () => {
// State
const items = ref<Item[]>([]);
const selectedItem = ref<Item | null>(null);
const isInitialized = ref(false);
// Init guard — prevent double initialization
async function initialize() {
if (isInitialized.value) return;
items.value = await featureApi.loadItems();
isInitialized.value = true;
}
// Actions
function selectItem(item: Item) {
selectedItem.value = item;
}
// Return everything that should be accessible
return { items, selectedItem, isInitialized, initialize, selectItem };
});
```
### API Layer Pattern
Centralized request function + module-level API objects:
```typescript
// services/apiClient.ts — single fetch wrapper, typed
export async function request<T>(path: string, options?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE}${path}`, {
headers: { 'Content-Type': 'application/json' },
...options
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Request failed' }));
throw new Error(error.error || 'Request failed');
}
return response.json();
}
// modules/inventory/api.ts — module-specific API
export const inventoryApi = {
listProducts: (warehouseId: string): Promise<Product[]> =>
request(`/warehouses/${warehouseId}/products`),
updateStock: (productId: string, quantity: number): Promise<void> =>
request(`/products/${productId}/stock`, {
method: 'PATCH',
body: JSON.stringify({ quantity })
})
};
```
### TypeScript Conventions
- **Strict mode** always on
- **No `any`** — use `unknown` with type narrowing instead
- **Discriminated unions** with type guard functions:
```typescript
type EditorTab =
| { type: 'file'; path: string; content: string }
| { type: 'terminal'; sessionId: string }
| { type: 'diff'; leftPath: string; rightPath: string };
function isFileTab(tab: EditorTab): tab is EditorTab & { type: 'file' } {
return tab.type === 'file';
}
```
- **Interfaces for object shapes**, types for unions and aliases
- **PascalCase** for types/interfaces, no `I` prefix (`User` not `IUser`)
### CSS / Styling
- Scoped styles in components: `<style scoped lang="scss">`
- CSS custom properties for theming: `var(--app-surface-color)`
- Dark theme by default
- kebab-case for CSS classes: `.file-tree-node`, `.panel-header`
- SCSS variables for design tokens (colors, spacing)
### Formatting (JS/TS/Vue)
```
Semicolons: required
Quotes: single quotes
Trailing commas: none
Line width: 100 characters
Indentation: 2 spaces
```
### General Code Style
- **`const` by default** — only use `let` when reassignment is needed, never `var`
- **Arrow functions** for most cases: `const handleClick = () => { ... }`
- **Nullish coalescing**: `value ?? fallback` over `value || fallback`
- **Optional chaining**: `obj?.property?.method?.()` over nested null checks
- **No magic numbers/strings** — use named constants
- **Colocate related code** — don't scatter logic across files unnecessarily
- **One blank line** between logical chunks for readability — no more than one
---
## Naming Reference
| Thing | Convention | Example |
|-------|-----------|---------|
| Vue components | PascalCase | `UserProfileCard.vue` |
| TS/JS files | camelCase | `userProfileService.ts` |
| Rust files | snake_case | `process_manager.rs` |
| C files | snake_case | `memory_allocator.c` |
| Swift files | PascalCase | `CameraManager.swift` |
| Config files | kebab-case | `quasar.config.js` |
| Directories | camelCase (JS/TS), snake_case (Rust/C) | `userAuth/`, `user_auth/` |
| Variables/functions | camelCase (JS/TS), snake_case (Rust/C), camelCase (Swift) | |
| Constants | UPPER_SNAKE_CASE | `MAX_RETRY_ATTEMPTS` |
| Types/Interfaces | PascalCase | `UserSession`, `FileTreeNode` |
| CSS classes | kebab-case | `.sidebar-panel-header` |
| Database tables | snake_case | `user_sessions` |
| Database columns | snake_case | `created_at`, `session_token` |

50
templates/docs/README.md Normal file
View File

@ -0,0 +1,50 @@
# Documentation
> Technical documentation for [Project Name]
## Table of Contents
### Getting Started
- [Installation & Setup](./getting-started.md)
### Features
<!-- Add feature docs as they're built. Format: [Feature Name](./feature-name.md) -->
### Architecture
- [Tech Stack & Architecture](./architecture.md)
### Development
- [Development Guide](./development.md)
- [Build & Deployment](./deployment.md)
---
## Documentation Guidelines
### Adding New Documentation
After completing a feature or phase, add documentation:
1. Create a new `.md` file in this folder using the naming convention below
2. Add a link to the Table of Contents above
3. Use the appropriate template as a starting point:
- [Feature template](./TEMPLATE-feature.md) - For documenting features
- [Build plan template](./TEMPLATE-build-plan.md) - For creating structured build plans
### Naming Convention
| Type | Pattern | Example |
|------|---------|---------|
| Feature docs | `feature-[name].md` | `feature-authentication.md` |
| Component docs | `component-[name].md` | `component-sidebar.md` |
| Integration docs | `integration-[name].md` | `integration-nhost.md` |
| Guide docs | `guide-[topic].md` | `guide-testing.md` |
| Build plans | `[name]-build-plan.md` | `auth-build-plan.md` |
### Documentation Structure
Each feature doc should include:
- **Overview** - What the feature does
- **Usage** - How to use it (with code examples)
- **Configuration** - Available options/settings
- **Related** - Links to related features/components

View File

@ -0,0 +1,121 @@
# [Project Name] - Build Plan
## Executor Config
```yaml
default_branch_prefix: feat/phase
test_before_commit: true
require_passing_tests: true
max_test_retries: 3
```
## Phase 1: [Phase Name]
**repo**: [repository-name]
**branch**: feat/phase-1-[slug]
### 1.1 [Task Name]
- **task**: [Description of what to do. Be specific and actionable.
Include context about existing code to use or patterns to follow.]
- **test**: `[command to verify success]`
- **commit**: "[Commit message]"
### 1.2 [Task Name]
- **task**: [Description]
- **test**: `[test command]`
- **commit**: "[Commit message]"
### 1.3 Documentation
- **task**: Update documentation for phase 1 changes.
Add any new files to docs/ folder.
- **test**: none
- **commit**: "Document phase 1"
---
## Phase 2: [Phase Name]
**repo**: [repository-name]
**branch**: feat/phase-2-[slug]
**depends_on**: Phase 1
### 2.1 [Task Name]
- **task**: [Description]
- **test**: `[test command]`
- **commit**: "[Commit message]"
### 2.2 Documentation
- **task**: Update documentation for phase 2 changes.
- **test**: none
- **commit**: "Document phase 2"
---
## Current Status
**Phase**: 1 - [Phase Name]
**Task**: [Current task]
---
## Template Instructions
> Remove this section when using this template
### Plan Format
Each plan consists of:
1. **Executor Config** (optional) - YAML block with settings
2. **Phases** - Major milestones (`## Phase N: Name`)
3. **Tasks** - Individual work items (`### N.N Task Name`)
### Phase Metadata
| Field | Required | Description |
|-------|----------|-------------|
| `**repo**` | Yes | Repository to work in |
| `**branch**` | No | Branch to create (default: `{prefix}-{N}`) |
| `**depends_on**` | No | Phase that must complete first |
### Task Fields
| Field | Required | Description |
|-------|----------|-------------|
| `- **task**` | Yes | What the agent should do |
| `- **test**` | No | Command to verify (`none` to skip) |
| `- **commit**` | No | Commit message (agent decides if omitted) |
### Writing Good Tasks
**Be specific:**
```markdown
# Bad
- **task**: Add authentication
# Good
- **task**: Add JWT authentication with /api/login and /api/logout endpoints.
Use bcrypt for password hashing. Store tokens in httpOnly cookies.
```
**Include context:**
```markdown
- **task**: Create dashboard component at /dashboard showing:
- User's recent activity (last 10 items)
- Account settings link
Use the existing UserCard component for the header.
```
**Use testable tests:**
```markdown
# Bad
- **test**: Check that it works
# Good
- **test**: `pnpm test && pnpm build`
```
### Tips
1. **One thing per task** - Don't combine unrelated work
2. **Tests are checkpoints** - Use them to verify progress
3. **Commits are savepoints** - Small, focused commits
4. **Phases are milestones** - Each should be independently useful
5. **End phases with docs** - Document as you go

View File

@ -0,0 +1,60 @@
# Feature Name
> Brief one-line description of what this feature does
## Overview
Explain what this feature does and why it exists. Include:
- The problem it solves
- Key capabilities
- When to use it
## Usage
### Basic Example
```typescript
// Code example showing basic usage
```
### Advanced Example
```typescript
// Code example showing advanced/common patterns
```
## Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `option1` | `string` | `""` | Description of option |
| `option2` | `boolean` | `false` | Description of option |
## Components
List the main components involved in this feature:
| Component | Location | Purpose |
|-----------|----------|---------|
| `ComponentName` | `src/components/ComponentName.vue` | Brief description |
## Data Flow
Describe how data flows through this feature (if applicable):
```
User Action → Component → Store → API → Database
↑ ↓
UI Update ← ← ← ← Response
```
## Related
- [Related Feature](./feature-related.md)
- [Related Component](./component-related.md)
## Changelog
| Date | Change |
|------|--------|
| YYYY-MM-DD | Initial implementation |

View File

@ -0,0 +1,61 @@
# Architecture
> Technical architecture and design decisions for [Project Name]
## Tech Stack
| Layer | Technology | Purpose |
|-------|------------|---------|
| Frontend | Vue 3 + Quasar | UI framework |
| State | Pinia | State management |
| Routing | Vue Router | Client-side routing |
| Desktop | Tauri | Native desktop wrapper |
| Backend | Nhost | Auth, database, storage |
## Project Structure
```
app/
├── src/
│ ├── boot/ # Initialization scripts
│ ├── components/ # Reusable Vue components
│ ├── composables/ # Vue composables (shared logic)
│ ├── layouts/ # Page layouts
│ ├── pages/ # Route pages
│ ├── router/ # Route definitions
│ ├── stores/ # Pinia stores
│ ├── services/ # API services
│ └── types/ # TypeScript types
├── src-tauri/ # Tauri/Rust code
└── build.sh # Build script
```
## Key Design Decisions
### [Decision 1]
**Context**: [Why this decision was needed]
**Decision**: [What was decided]
**Consequences**: [Trade-offs and implications]
### [Decision 2]
...
## Data Model
[Describe main entities and relationships]
```
Entity1 ──────< Entity2
│
└──────< Entity3
```
## External Integrations
| Service | Purpose | Documentation |
|---------|---------|---------------|
| [Service] | [Purpose] | [Link] |

View File

@ -0,0 +1,78 @@
# Build & Deployment
> Build process and deployment instructions for [Project Name]
## Build Commands
```bash
cd app
# Development build
./build.sh -dev -open
# Production build (installs to ~/Applications)
./build.sh -prod
# Clean build artifacts
./build.sh clean
```
## Build Output
```
app/build/
├── dev/
│ └── [AppName].app # Development build
├── prod/
│ └── [AppName].app # Production build
├── logs/
│ └── build_latest.log # Build output
└── console/
└── console_latest.log # Runtime logs
```
## Production Build
### macOS
1. Build the production version:
```bash
./build.sh -prod
```
2. The app is automatically installed to `~/Applications/`
### Distribution
[Add distribution instructions - notarization, signing, etc.]
## TeamIDE Deployment
If using TeamIDE for hosting:
```bash
# Deploy changes
teamide_deploy
# Set environment variables
teamide_set_env_vars
# Check logs
teamide_get_logs
```
**Workspace ID**: `[your-workspace-id]`
## Environment Variables
### Required for Production
```env
# List production environment variables
```
### Optional
```env
# List optional environment variables
```

View File

@ -0,0 +1,72 @@
# Development Guide
> Development workflow and practices for [Project Name]
## Development Workflow
### Build and Run
This project uses a **build-and-run** approach (not hot reload):
```bash
cd app
./build.sh -dev -open # Build and launch
./build.sh logs # View console output
```
### Why Build-and-Run?
- **Dev = Prod** - What you test is what ships
- **Full logging** - Console output captured to files
- **No dev-only bugs** - Identical to production environment
## Making Changes
### Adding a New Feature
1. Create components in `src/components/`
2. Add pages in `src/pages/`
3. Register routes in `src/router/`
4. Add state management in `src/stores/`
5. **Rebuild to test**: `./build.sh -dev -open`
6. **Document the feature** in `docs/`
### Adding a Tauri Command
1. Add the command in `src-tauri/src/main.rs`
2. Register it in the `invoke_handler`
3. Call from frontend with `invoke('command_name', { args })`
## Debugging
### Console Logs
```bash
./build.sh logs # Last 100 lines
cat build/console/console_latest.log # Full log
```
### Build Errors
```bash
cat build/logs/build_latest.log
```
### Common Issues
| Issue | Solution |
|-------|----------|
| Build fails with cache errors | `./build.sh clean && ./build.sh -dev -open` |
| Changes not appearing | Rebuild - this is build-and-run, not hot reload |
| Console logs missing | Check `console-logger.ts` is in boot files |
## Code Style
- **Components**: PascalCase (`UserProfile.vue`)
- **Composables**: camelCase with `use` prefix (`useAuth.ts`)
- **Stores**: camelCase (`userStore.ts`)
- **Types**: PascalCase (`UserProfile.ts`)
## Testing
[Add testing instructions when implemented]

View File

@ -0,0 +1,52 @@
# Getting Started
> Installation and initial setup guide for [Project Name]
## Prerequisites
- Node.js 18+
- npm
- [Additional prerequisites]
## Installation
```bash
# Clone the repository
git clone https://github.com/dcherrera/[ProjectName].git
cd [ProjectName]/app
# Install dependencies
npm install
```
## Configuration
1. Copy the environment template:
```bash
cp .env.example .env
```
2. Configure required environment variables:
```env
# Required
VARIABLE_NAME=value
```
## Running the App
### Development
```bash
./build.sh -dev -open
```
### Production
```bash
./build.sh -prod
```
## Next Steps
- [Architecture Overview](./architecture.md) - Understand the codebase structure
- [Development Guide](./development.md) - Learn the development workflow

View File

@ -0,0 +1,108 @@
/**
* Console Logger Boot File (Tauri)
*
* Intercepts console.log/warn/error/info/debug and forwards them to the
* Rust backend via Tauri invoke. This allows all frontend console output
* to be captured in the build.sh console logs.
*
* The original console methods are preserved and still work normally,
* so you'll see output in both the WebView devtools AND the terminal logs.
*
* SETUP:
* 1. Copy this file to: app/src/boot/console-logger.ts
* 2. Add 'console-logger' to boot array in quasar.config.js
* 3. Add log_to_stdout command to Rust (see below)
* 4. Add chrono = "0.4" to Cargo.toml dependencies
*
* RUST COMMAND (add to src-tauri/src/commands.rs):
*
* use chrono::Local;
*
* #[tauri::command]
* pub fn log_to_stdout(level: String, message: String) {
* let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
* let prefix = match level.as_str() {
* "error" => "[ERROR]",
* "warn" => "[WARN]",
* "info" => "[INFO]",
* "debug" => "[DEBUG]",
* _ => "[LOG]",
* };
* println!("[{}] {} {}", timestamp, prefix, message);
* }
*
* Don't forget to register it in lib.rs:
* commands::log_to_stdout,
*/
import { invoke } from '@tauri-apps/api/core';
// Store original console methods
const originalConsole = {
log: console.log.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console),
info: console.info.bind(console),
debug: console.debug.bind(console),
};
// Helper to stringify arguments
function formatArgs(args: unknown[]): string {
return args
.map((arg) => {
if (typeof arg === 'string') return arg;
if (arg instanceof Error) return `${arg.name}: ${arg.message}\n${arg.stack}`;
try {
return JSON.stringify(arg, null, 2);
} catch {
return String(arg);
}
})
.join(' ');
}
// Forward to Rust backend
function logToBackend(level: string, args: unknown[]) {
const message = formatArgs(args);
invoke('log_to_stdout', { level, message }).catch(() => {
// Silently fail if Tauri isn't available (e.g., in browser dev mode)
});
}
// Override console methods
console.log = (...args: unknown[]) => {
originalConsole.log(...args);
logToBackend('log', args);
};
console.warn = (...args: unknown[]) => {
originalConsole.warn(...args);
logToBackend('warn', args);
};
console.error = (...args: unknown[]) => {
originalConsole.error(...args);
logToBackend('error', args);
};
console.info = (...args: unknown[]) => {
originalConsole.info(...args);
logToBackend('info', args);
};
console.debug = (...args: unknown[]) => {
originalConsole.debug(...args);
logToBackend('debug', args);
};
// Also capture unhandled errors and promise rejections
window.addEventListener('error', (event) => {
logToBackend('error', [`Unhandled error: ${event.message} at ${event.filename}:${event.lineno}`]);
});
window.addEventListener('unhandledrejection', (event) => {
logToBackend('error', [`Unhandled promise rejection: ${event.reason}`]);
});
// Log that the console logger is active
console.info('[Console Logger] Active - all console output will be logged to stdout');

View File

@ -0,0 +1,11 @@
# .teamide/ Template
Template files for TeamIDE's project management system. When scaffolding a new project from Ideation, copy `project.yml` into the project's `.teamide/` directory and update the `name` field.
Plan and task files are not included here because they are generated dynamically from each project's `build_plan.md` content. See the "TeamIDE PM Files" section in `claude.md` for the conversion rules.
## Files
| File | Purpose |
|------|---------|
| `project.yml` | Project configuration with board columns and discovery ignore patterns. Replace `PROJECT_NAME` with the actual project name. |

View File

@ -0,0 +1,25 @@
name: PROJECT_NAME
status: active
team: []
labels: []
references: []
board_columns:
tasks:
- backlog
- todo
- in-progress
- review
- done
plans:
- draft
- active
- complete
discovery_ignore:
- node_modules
- .git
- dist
- build
- vendor
- __pycache__
- .venv
- target