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.
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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/
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# Project Name
|
||||
|
||||
> One-line tagline describing what this project does
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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]
|
||||
|
|
@ -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` |
|
||||
|
|
@ -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]
|
||||
```
|
||||
|
|
@ -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]
|
||||
|
|
@ -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] |
|
||||
|
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
-->
|
||||
|
|
@ -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]** |
|
||||
|
|
@ -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]
|
||||
|
|
@ -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/
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ""
|
||||
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
|
After Width: | Height: | Size: 299 B |
|
After Width: | Height: | Size: 855 B |
|
After Width: | Height: | Size: 104 B |
|
After Width: | Height: | Size: 239 B |
|
After Width: | Height: | Size: 331 B |
|
After Width: | Height: | Size: 349 B |
|
After Width: | Height: | Size: 975 B |
|
After Width: | Height: | Size: 103 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 121 B |
|
After Width: | Height: | Size: 171 B |
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 130 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -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.
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// App root component
|
||||
</script>
|
||||
|
|
@ -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']"}
|
||||
```
|
||||
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
// App-specific styles
|
||||
// Add your custom styles here
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
})
|
||||
|
|
@ -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"]
|
||||
}
|
||||
|
|
@ -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_']
|
||||
})
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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] |
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ""
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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` |
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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] |
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
|
|
@ -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');
|
||||
|
|
@ -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. |
|
||||
|
|
@ -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
|
||||