Compare commits
8 Commits
v0.7.0-alp
...
devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
2874ea3be1
|
|||
|
26bc33a095
|
|||
|
67c7a3c050
|
|||
|
5d08b1c927
|
|||
|
148b8ff49d
|
|||
|
c4113f63a4
|
|||
|
6274e4936d
|
|||
|
e1259688a4
|
8
.claude/commands/handoff.md
Normal file
8
.claude/commands/handoff.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Write a session handoff file for the current session.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
1. Read `templates/claude-templates.md` and find the Session Handoff template (Template 4). Use the Light Handoff if this is a small project (under 5 sessions), Full Handoff otherwise.
|
||||||
|
2. Fill in every field based on what was accomplished in this session. Be specific — include exact file paths for every output, exact numbers discovered, and conditional logic established.
|
||||||
|
3. Write the handoff to `./docs/summaries/handoff-[today's date]-[topic].md`.
|
||||||
|
4. If a previous handoff file exists in `./docs/summaries/`, move it to `./docs/archive/handoffs/`.
|
||||||
|
5. Tell me the file path of the new handoff and summarize what it contains.
|
||||||
13
.claude/commands/process-doc.md
Normal file
13
.claude/commands/process-doc.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Process an input document into a structured source summary.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
1. Read `templates/claude-templates.md` and find the Source Document Summary template (Template 1). Use the Light Source Summary if this is a small project (under 5 sessions), Full Source Summary otherwise.
|
||||||
|
2. Read the document at: $ARGUMENTS
|
||||||
|
3. Extract all information into the template format. Pay special attention to:
|
||||||
|
- EXACT numbers — do not round or paraphrase
|
||||||
|
- Requirements in IF/THEN/BUT/EXCEPT format
|
||||||
|
- Decisions with rationale and rejected alternatives
|
||||||
|
- Open questions marked as OPEN, ASSUMED, or MISSING
|
||||||
|
4. Write the summary to `./docs/summaries/source-[filename].md`.
|
||||||
|
5. Move the original document to `./docs/archive/`.
|
||||||
|
6. Tell me: what was extracted, what's unclear, and what needs follow-up.
|
||||||
13
.claude/commands/status.md
Normal file
13
.claude/commands/status.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Report on the current project state.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
1. Read `./docs/summaries/00-project-brief.md` for project context.
|
||||||
|
2. Find and read the latest `handoff-*.md` file in `./docs/summaries/` for current state.
|
||||||
|
3. List all files in `./docs/summaries/` to understand what's been processed.
|
||||||
|
4. Report:
|
||||||
|
- **Project:** name and type from the project brief
|
||||||
|
- **Current phase:** based on the project phase tracker
|
||||||
|
- **Last session:** what was accomplished (from the latest handoff)
|
||||||
|
- **Next steps:** what the next session should do (from the latest handoff)
|
||||||
|
- **Open questions:** anything unresolved
|
||||||
|
- **Summary file count:** how many files in docs/summaries/ (warn if approaching 15)
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/dist
|
|
||||||
/src-capacitor
|
|
||||||
/src-cordova
|
|
||||||
/.quasar
|
|
||||||
/node_modules
|
|
||||||
.eslintrc.js
|
|
||||||
/src-ssr
|
|
||||||
/quasar.config.*.temporary.compiled*
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
|
|
||||||
// This option interrupts the configuration hierarchy at this file
|
|
||||||
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
|
|
||||||
root: true,
|
|
||||||
|
|
||||||
// https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
|
|
||||||
// Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
|
|
||||||
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
|
|
||||||
parserOptions: {
|
|
||||||
parser: require.resolve('@typescript-eslint/parser'),
|
|
||||||
extraFileExtensions: [ '.vue' ]
|
|
||||||
},
|
|
||||||
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2021: true,
|
|
||||||
node: true,
|
|
||||||
'vue/setup-compiler-macros': true
|
|
||||||
},
|
|
||||||
|
|
||||||
// Rules order is important, please avoid shuffling them
|
|
||||||
extends: [
|
|
||||||
// Base ESLint recommended rules
|
|
||||||
// 'eslint:recommended',
|
|
||||||
|
|
||||||
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
|
|
||||||
// ESLint typescript rules
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
|
|
||||||
// Uncomment any of the lines below to choose desired strictness,
|
|
||||||
// but leave only one uncommented!
|
|
||||||
// See https://eslint.vuejs.org/rules/#available-rules
|
|
||||||
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
|
|
||||||
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
|
|
||||||
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
|
|
||||||
|
|
||||||
// https://github.com/prettier/eslint-config-prettier#installation
|
|
||||||
// usage with Prettier, provided by 'eslint-config-prettier'.
|
|
||||||
'prettier'
|
|
||||||
],
|
|
||||||
|
|
||||||
plugins: [
|
|
||||||
// required to apply rules which need type information
|
|
||||||
'@typescript-eslint',
|
|
||||||
|
|
||||||
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
|
|
||||||
// required to lint *.vue files
|
|
||||||
'vue'
|
|
||||||
|
|
||||||
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
|
|
||||||
// Prettier has not been included as plugin to avoid performance impact
|
|
||||||
// add it as an extension for your IDE
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
globals: {
|
|
||||||
ga: 'readonly', // Google Analytics
|
|
||||||
cordova: 'readonly',
|
|
||||||
__statics: 'readonly',
|
|
||||||
__QUASAR_SSR__: 'readonly',
|
|
||||||
__QUASAR_SSR_SERVER__: 'readonly',
|
|
||||||
__QUASAR_SSR_CLIENT__: 'readonly',
|
|
||||||
__QUASAR_SSR_PWA__: 'readonly',
|
|
||||||
process: 'readonly',
|
|
||||||
Capacitor: 'readonly',
|
|
||||||
chrome: 'readonly'
|
|
||||||
},
|
|
||||||
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
|
|
||||||
'prefer-promise-reject-errors': 'off',
|
|
||||||
|
|
||||||
quotes: ['warn', 'single', { avoidEscape: true }],
|
|
||||||
|
|
||||||
// this rule, if on, would require explicit return type on the `render` function
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
|
|
||||||
// in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled
|
|
||||||
'@typescript-eslint/no-var-requires': 'off',
|
|
||||||
|
|
||||||
// The core 'no-unused-vars' rules (in the eslint:recommended ruleset)
|
|
||||||
// does not work with type definitions
|
|
||||||
'no-unused-vars': 'off',
|
|
||||||
|
|
||||||
// allow debugger during development only
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- alpha
|
- alpha
|
||||||
|
- devel
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -17,7 +18,7 @@ jobs:
|
|||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: "20.x"
|
||||||
- name: Install yarn
|
- name: Install yarn
|
||||||
run: npm install --global yarn
|
run: npm install --global yarn
|
||||||
- name: Install yarn dependencies
|
- name: Install yarn dependencies
|
||||||
|
|||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -21,6 +21,12 @@ node_modules
|
|||||||
/src-bex/www
|
/src-bex/www
|
||||||
/src-bex/js/core
|
/src-bex/js/core
|
||||||
|
|
||||||
|
# Yarn 4
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
# Log files
|
# Log files
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
@@ -37,7 +43,10 @@ yarn-error.log*
|
|||||||
.env*
|
.env*
|
||||||
|
|
||||||
# version file
|
# version file
|
||||||
src/version.js
|
src/version.ts
|
||||||
VERSION
|
VERSION
|
||||||
release-*.gz
|
release-*.gz
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
|
||||||
|
# Quasar cruft
|
||||||
|
/quasar.config.*.temporary.compiled*
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"main",
|
"main",
|
||||||
"next",
|
"next",
|
||||||
{ "name": "beta", "prerelease": true },
|
{ "name": "beta", "prerelease": true },
|
||||||
|
{ "name": "devel", "prerelease": true },
|
||||||
{ "name": "alpha", "prerelease": true }
|
{ "name": "alpha", "prerelease": true }
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
|||||||
940
.yarn/releases/yarn-4.13.0.cjs
vendored
Executable file
940
.yarn/releases/yarn-4.13.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
3
.yarnrc.yml
Normal file
3
.yarnrc.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
nodeLinker: node-modules
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-4.13.0.cjs
|
||||||
51
CLAUDE.md
Normal file
51
CLAUDE.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
## Session Start
|
||||||
|
|
||||||
|
Read the latest handoff in docs/summaries/ if one exists. Load only the files that handoff references — not all summaries. If no handoff exists, ask: what is the project, what type of work, what is the target deliverable.
|
||||||
|
|
||||||
|
Before starting work, state: what you understand the project state to be, what you plan to do this session, and any open questions.
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
You work with Patrick, a Solutions Architect, on the OYS Borrow a Boat (bab-app) project — a Quasar/Vue 3 app for managing a Borrow a Boat program for a Yacht Club. Backend is Appwrite.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
- **App**: OYS Borrow a Boat (oys_bab)
|
||||||
|
- **Stack**: Quasar (Vue 3), TypeScript, Appwrite (BaaS)
|
||||||
|
- **Purpose**: Manage a Borrow a Boat program for a Yacht Club
|
||||||
|
- **Docs**: docs/planning/ contains personas, user/role/permission model, and time-based logic
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. Do not mix unrelated project contexts in one session.
|
||||||
|
2. Write state to disk, not conversation. After completing meaningful work, write a summary to docs/summaries/ using templates from templates/claude-templates.md. Include: decisions with rationale, exact numbers, file paths, open items.
|
||||||
|
3. Before compaction or session end, write to disk: every number, every decision with rationale, every open question, every file path, exact next action.
|
||||||
|
4. When switching work types (research → writing → review), write a handoff to docs/summaries/handoff-[date]-[topic].md and suggest a new session.
|
||||||
|
5. Do not silently resolve open questions. Mark them OPEN or ASSUMED.
|
||||||
|
6. Do not bulk-read documents. Process one at a time: read, summarize to disk, release from context before reading next. For the detailed protocol, read docs/context/processing-protocol.md.
|
||||||
|
7. Sub-agent returns must be structured, not free-form prose. Use output contracts from templates/claude-templates.md.
|
||||||
|
|
||||||
|
## Where Things Live
|
||||||
|
|
||||||
|
- templates/claude-templates.md — summary, handoff, decision, analysis, task, output contract templates (read on demand)
|
||||||
|
- docs/summaries/ — active session state (latest handoff + project brief + decision records + source summaries)
|
||||||
|
- docs/context/ — reusable domain knowledge, loaded only when relevant to the current task
|
||||||
|
- processing-protocol.md — full document processing steps
|
||||||
|
- archive-rules.md — summary lifecycle and file archival rules
|
||||||
|
- subagent-rules.md — rules for structured sub-agent outputs
|
||||||
|
- docs/planning/ — original planning documents (personas, roles/permissions, time logic)
|
||||||
|
- docs/archive/ — processed raw files. Do not read unless explicitly told.
|
||||||
|
- output/deliverables/ — final outputs
|
||||||
|
- src/ — Quasar/Vue app source
|
||||||
|
- src-pwa/ — PWA config
|
||||||
|
- appwrite.json — Appwrite project config
|
||||||
|
|
||||||
|
## Error Recovery
|
||||||
|
|
||||||
|
If context degrades or auto-compact fires unexpectedly: write current state to docs/summaries/recovery-[date].md, tell the user what may have been lost, suggest a fresh session.
|
||||||
|
|
||||||
|
## Before Delivering Output
|
||||||
|
|
||||||
|
Verify: exact numbers preserved, open questions marked OPEN, output matches what was requested (not assumed), claims backed by specific data, output consistent with stored decisions in docs/context/, summary written to disk for this session's work.
|
||||||
88
docs/archive/handoffs/handoff-2026-03-15-auth-magic-link.md
Normal file
88
docs/archive/handoffs/handoff-2026-03-15-auth-magic-link.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Session Handoff: Auth Refactor — Magic Link & Cleanup
|
||||||
|
**Date:** 2026-03-15
|
||||||
|
**Session Duration:** ~1 hour
|
||||||
|
**Session Focus:** Remove Google/Discord OAuth, add magic link login, add About dialog
|
||||||
|
**Context Usage at Handoff:** ~30%
|
||||||
|
|
||||||
|
## What Was Accomplished
|
||||||
|
|
||||||
|
1. Analyzed full auth flow (store, boot, login page, router guard) → no output file, inline analysis
|
||||||
|
2. Removed Google OAuth → deleted `src/components/GoogleOauthComponent.vue`
|
||||||
|
3. Removed Discord OAuth → deleted `src/components/DiscordOauthComponent.vue`
|
||||||
|
4. Removed `googleLogin`, `discordLogin` from auth store → `src/stores/auth.ts`
|
||||||
|
5. Removed `OAuthProvider` import from auth store → `src/stores/auth.ts`
|
||||||
|
6. Added `createMagicURLSession()` to auth store (calls `account.createMagicURLToken`) → `src/stores/auth.ts`
|
||||||
|
7. Added `magicURLLogin()` to auth store (calls `account.updateMagicURLSession`) → `src/stores/auth.ts`
|
||||||
|
8. Updated `LoginPage.vue` — removed OAuth component imports/usage, added "Send Magic Link" button, added `onMounted` handler to detect magic link callback params and call `magicURLLogin` → `src/pages/LoginPage.vue`
|
||||||
|
9. Added "About" item to left drawer → `src/components/LeftDrawer.vue` — opens a Quasar Dialog with app name, version, description
|
||||||
|
10. Converted `src/version.js` → `src/version.ts` to eliminate TS hint
|
||||||
|
11. Updated `generate-version.js` to write to `src/version.ts` → `generate-version.js`
|
||||||
|
12. Fixed stale import in `SignupPage.vue` (`src/version.js` → `src/version`) → `src/pages/SignupPage.vue`
|
||||||
|
13. Fixed `LeftDrawer.vue` import path `boot/appwrite` → `src/boot/appwrite` (was a TS module resolution error)
|
||||||
|
|
||||||
|
## Exact State of Work in Progress
|
||||||
|
|
||||||
|
- `.env.local` not being picked up by `quasar dev`: user interrupted the fix (was about to add `require('dotenv').config(...)` to `quasar.config.js`). **Status: UNRESOLVED.** User stopped this change — may prefer a different approach or wants to investigate themselves.
|
||||||
|
|
||||||
|
## Decisions Made This Session
|
||||||
|
|
||||||
|
- Use `account.updateMagicURLSession(userId, secret)` (not `createSession`) for magic link completion — BECAUSE Appwrite SDK v14 uses a separate method for magic URL vs OTP token sessions. `createSession` is for OTP only.
|
||||||
|
- Magic link callback URL = `window.location.origin + '/login'` — Appwrite appends `?userId=xxx&secret=xxx` and the `onMounted` handler in LoginPage detects and consumes these.
|
||||||
|
- Keep OTP code flow alongside magic link — user did not ask to remove it; both are available.
|
||||||
|
- About dialog placed in LeftDrawer (not a separate page) — appropriate pattern for simple info display in a mobile app.
|
||||||
|
|
||||||
|
## Key Numbers Generated or Discovered This Session
|
||||||
|
|
||||||
|
- Appwrite SDK version: `^14.0.1`
|
||||||
|
- `@quasar/app-vite` version: `^1.9.1`
|
||||||
|
- App version string source: `src/version.ts`, written by `generate-version.js` (takes version as CLI arg)
|
||||||
|
- Dev server port: `4000` (set in `quasar.config.js` `devServer.strictport`)
|
||||||
|
|
||||||
|
## Conditional Logic Established
|
||||||
|
|
||||||
|
- IF magic link callback detected (`query.userId && query.secret` in route on LoginPage mount) THEN call `magicURLLogin(userId, secret)` BECAUSE this uses `updateMagicURLSession` which is the correct Appwrite v14 API.
|
||||||
|
- IF user enters email and clicks "Send Code" THEN OTP flow runs (6-digit code emailed, entered in second input field).
|
||||||
|
- IF user enters email and clicks "Send Magic Link" THEN magic link email sent; user clicks link; page reloads at `/login?userId=xxx&secret=xxx`; `onMounted` auto-completes login.
|
||||||
|
|
||||||
|
## Files Created or Modified
|
||||||
|
|
||||||
|
| File Path | Action | Description |
|
||||||
|
|-----------|--------|-------------|
|
||||||
|
| `src/stores/auth.ts` | Modified | Removed `googleLogin`, `discordLogin`, `OAuthProvider` import; added `createMagicURLSession`, `magicURLLogin` |
|
||||||
|
| `src/pages/LoginPage.vue` | Modified | Removed OAuth components; added magic link button and `onMounted` callback handler |
|
||||||
|
| `src/components/LeftDrawer.vue` | Modified | Added "About" menu item that opens info dialog with version; fixed boot import path |
|
||||||
|
| `src/components/GoogleOauthComponent.vue` | Deleted | No longer used |
|
||||||
|
| `src/components/DiscordOauthComponent.vue` | Deleted | No longer used |
|
||||||
|
| `src/version.ts` | Renamed (was `.js`) | Eliminates TypeScript implicit-any hint |
|
||||||
|
| `src/pages/SignupPage.vue` | Modified | Updated `version.js` import to `version` |
|
||||||
|
| `generate-version.js` | Modified | Now writes to `src/version.ts` instead of `src/version.js` |
|
||||||
|
|
||||||
|
## What the NEXT Session Should Do
|
||||||
|
|
||||||
|
1. **First**: Verify magic link flow end-to-end in dev environment (send link, click it, confirm auto-login works)
|
||||||
|
2. **Then**: Resolve `.env.local` not being picked up — options are: (a) add `require('dotenv').config({ path: '.env.local' })` to top of `quasar.config.js`, or (b) use `.env` instead of `.env.local`, or (c) investigate if `@quasar/app-vite` 1.9.x has a bug
|
||||||
|
3. **Then**: Check if `SignupPage.vue` / `register()` flow is still intended — it creates email+password accounts but the login page only offers passwordless flows; this is an inconsistency
|
||||||
|
|
||||||
|
## Open Questions Requiring User Input
|
||||||
|
|
||||||
|
- [ ] Should the OTP (6-digit code) flow be kept, or replaced entirely by magic link? — impacts LoginPage UX
|
||||||
|
- [ ] Should the SignupPage (email+password registration) be removed in favour of magic link only? — impacts `src/pages/SignupPage.vue`, `src/stores/auth.ts` `register()`, router `/signup` route
|
||||||
|
- [ ] Should "Forgot Password?" link be removed from LoginPage now that magic link is the primary flow? — it was already removed from LoginPage in this session (not present in current code)
|
||||||
|
- [ ] `.env.local` fix approach — user stopped the `dotenv` approach; confirm preferred method
|
||||||
|
|
||||||
|
## Assumptions That Need Validation
|
||||||
|
|
||||||
|
- ASSUMED: `window.location.origin` is correct for magic link callback URL in all deployment environments — validate that prod URL matches what Appwrite Console has whitelisted as a redirect domain
|
||||||
|
- ASSUMED: Appwrite project has magic URL tokens enabled — validate in Appwrite Console → Auth settings
|
||||||
|
|
||||||
|
## What NOT to Re-Read
|
||||||
|
|
||||||
|
- `src/components/GoogleOauthComponent.vue` — deleted
|
||||||
|
- `src/components/DiscordOauthComponent.vue` — deleted
|
||||||
|
|
||||||
|
## Files to Load Next Session
|
||||||
|
|
||||||
|
- `src/stores/auth.ts` — primary auth logic, fully refactored this session
|
||||||
|
- `src/pages/LoginPage.vue` — magic link + OTP UI, modified this session
|
||||||
|
- `src/boot/appwrite.ts` — contains duplicate `login()` function (email/password) that may be dead code post-refactor
|
||||||
|
- `quasar.config.js` — if resolving `.env.local` issue
|
||||||
114
docs/archive/handoffs/handoff-2026-03-15-dependency-updates.md
Normal file
114
docs/archive/handoffs/handoff-2026-03-15-dependency-updates.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# Session Handoff: Dependency Updates & ESLint Cleanup
|
||||||
|
**Date:** 2026-03-15
|
||||||
|
**Session Focus:** Complete Quasar v1→v2 migration, Appwrite SDK v14→v23 update, ESLint flat config cleanup
|
||||||
|
**Status:** BUILD PASSING — 0 errors, 0 warnings
|
||||||
|
|
||||||
|
## What Was Accomplished
|
||||||
|
|
||||||
|
### From prior sessions (captured in archived handoffs):
|
||||||
|
1. Auth flow refactored to passwordless (magic link + OTP), OAuth removed
|
||||||
|
2. Google/Discord OAuth components deleted
|
||||||
|
3. About dialog with version info added → `src/components/LeftDrawer.vue`
|
||||||
|
4. `quasar.config.js` → `quasar.config.ts` (ESM TypeScript)
|
||||||
|
5. `"type": "module"` added to `package.json`
|
||||||
|
6. Yarn 1.x → Yarn 4.13.0
|
||||||
|
7. ESLint legacy `.eslintrc.cjs` → flat config `eslint.config.js`
|
||||||
|
8. QCalendar app extension removed → direct npm package import
|
||||||
|
9. Boot/router/store wrappers updated to `#q-app/wrappers` imports
|
||||||
|
10. Appwrite SDK updated v14.0.1 → v23.0.0
|
||||||
|
11. `globals` package installed; browser + ES2021 globals added to ESLint config
|
||||||
|
|
||||||
|
### This session (build cleanup — all 30 TS errors + 12 ESLint issues resolved):
|
||||||
|
|
||||||
|
**TypeScript `as unknown as` casts** (Appwrite v23 `DefaultDocument` no longer overlaps domain types):
|
||||||
|
- `src/stores/boat.ts:36` — `as unknown as Boat[]`
|
||||||
|
- `src/stores/interval.ts:95,113,127` — `as unknown as Interval`
|
||||||
|
- `src/stores/intervalTemplate.ts` — map callback cast + `as unknown as IntervalTemplate` (3 places)
|
||||||
|
- `src/stores/reservation.ts:65,80,247` — `as unknown as Reservation`
|
||||||
|
- `src/stores/task.ts:53,65,77,109,132` — `as unknown as Task[]`, `TaskTag[]`, `SkillTag[]`, `Task`
|
||||||
|
|
||||||
|
**`.id` → `.$id` fixes** (Appwrite uses `$id`, not `id`):
|
||||||
|
- `src/components/boat/BoatPreviewComponent.vue:7`
|
||||||
|
- `src/components/scheduling/boat/BoatScheduleTableComponent.vue:54`
|
||||||
|
- `src/components/task/TaskListComponent.vue:4`
|
||||||
|
- `src/pages/schedule/ManageCalendar.vue:40`
|
||||||
|
- `src/stores/sampledata/schedule.ts:19,29,138` — also `id:` → `$id:` in object literals
|
||||||
|
|
||||||
|
**`defineProps` import conflict** (auto-imported in `<script setup>`, cannot also be explicitly imported):
|
||||||
|
- `src/components/task/TaskCardComponent.vue:20` — removed import; also removed `subtasks` template refs (not in Task type)
|
||||||
|
- `src/components/task/TaskTableComponent.vue:215` — removed `defineProps` from import
|
||||||
|
|
||||||
|
**ESLint fixes:**
|
||||||
|
- `eslint.config.js:52` — `process.env.NODE_ENV === 'production' ? 'error' : 'off'` → `'off'` (process not defined in .js ESLint globals)
|
||||||
|
- `src/components/ResourceScheduleViewerComponent.vue:173` — removed `|| undefined` (always truthy)
|
||||||
|
- `src/components/ResourceScheduleViewerComponent.vue:177` — `catch (e)` → `catch { }` (unused binding)
|
||||||
|
- `src/components/ResourceScheduleViewerComponent.vue:237-247` — removed unused `eslint-disable-next-line` comments
|
||||||
|
- `src/components/scheduling/boat/BoatScheduleTableComponent.vue:116` — `NodeJS.Timeout` → `ReturnType<typeof setInterval>`
|
||||||
|
- `src/components/scheduling/boat/BoatScheduleTableComponent.vue:129` — ternary as statement → `if/else`; also destructured `{ direction }` to fix unused `event` hint
|
||||||
|
- `src/pages/LoginPage.vue:131` — `catch (e)` → `catch { }`
|
||||||
|
|
||||||
|
**Other fixes:**
|
||||||
|
- `src-pwa/register-service-worker.ts` — installed `register-service-worker` package (was missing from package.json)
|
||||||
|
- `src/stores/sampledata/schedule.ts:50` — `template.blocks` → `template.timeTuples` (property was renamed)
|
||||||
|
- `src/stores/sampledata/schedule.ts:145` — removed `reservationDate` (not in Reservation type)
|
||||||
|
- `src/stores/intervalTemplate.ts:27` — `d.timeTuple` cast via `as unknown as { timeTuple: string[] }`
|
||||||
|
- `src/stores/intervalTemplate.ts:82` — `response.timeTuple` cast via `as unknown as { timeTuple: string[] }`
|
||||||
|
- `src/components/scheduling/boat/CalendarHeaderComponent.vue` — removed `getWeekdaySkips` (removed from qcalendar API); `createDayList` now takes `weekdays` directly as 4th arg; removed `weekdaySkips` computed
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **`as unknown as Type` pattern** — correct approach for Appwrite v23 `DefaultDocument` casts; v23 made `DefaultDocument` strict, no longer assignable to domain types without double-cast — STATUS: CONFIRMED
|
||||||
|
- **`getWeekdaySkips` removed from qcalendar** — `createDayList` now accepts `weekdays` array directly as 4th param — STATUS: CONFIRMED (verified from ESM source)
|
||||||
|
- **`subtasks` removed from TaskCardComponent template** — `Task` type has no `subtasks` field; feature was dead code — STATUS: ASSUMED safe (see open question)
|
||||||
|
- **`no-debugger: 'off'`** — hardcoded instead of `process.env.NODE_ENV` conditional because `process` is not in ESLint globals for `.js` files — STATUS: CONFIRMED
|
||||||
|
|
||||||
|
## Key Numbers
|
||||||
|
|
||||||
|
- TS errors at session start: 30 (in 14 files)
|
||||||
|
- ESLint errors at session start: 12 (6 errors, 6 warnings)
|
||||||
|
- TS errors at session end: 0
|
||||||
|
- ESLint errors at session end: 0
|
||||||
|
- Packages added: `register-service-worker`
|
||||||
|
|
||||||
|
## Files Modified This Session
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `eslint.config.js` | `no-debugger` rule hardcoded to `'off'` |
|
||||||
|
| `src/components/ResourceScheduleViewerComponent.vue` | 3 ESLint fixes |
|
||||||
|
| `src/components/scheduling/boat/BoatScheduleTableComponent.vue` | `.id`→`.$id`, NodeJS.Timeout, if/else |
|
||||||
|
| `src/components/scheduling/boat/CalendarHeaderComponent.vue` | Removed `getWeekdaySkips`; updated `createDayList` call |
|
||||||
|
| `src/pages/LoginPage.vue` | `catch { }` |
|
||||||
|
| `src/components/boat/BoatPreviewComponent.vue` | `.id`→`.$id` |
|
||||||
|
| `src/components/task/TaskCardComponent.vue` | Removed `defineProps` import + subtasks refs |
|
||||||
|
| `src/components/task/TaskListComponent.vue` | `.id`→`.$id` |
|
||||||
|
| `src/components/task/TaskTableComponent.vue` | Removed `defineProps` from import |
|
||||||
|
| `src/pages/schedule/ManageCalendar.vue` | `.id`→`.$id` |
|
||||||
|
| `src/stores/boat.ts` | `as unknown as Boat[]` |
|
||||||
|
| `src/stores/interval.ts` | `as unknown as Interval` (3 places) |
|
||||||
|
| `src/stores/intervalTemplate.ts` | Multiple cast fixes + `timeTuple` access |
|
||||||
|
| `src/stores/reservation.ts` | `as unknown as Reservation` (multiple) |
|
||||||
|
| `src/stores/sampledata/schedule.ts` | `id`→`$id`, `blocks`→`timeTuples`, removed `reservationDate` |
|
||||||
|
| `src/stores/task.ts` | `as unknown as` for Task/TaskTag/SkillTag |
|
||||||
|
| `package.json` / `yarn.lock` | Added `register-service-worker` |
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- [ ] `src/components/task/TaskCardComponent.vue` — `subtasks` removed from template. Should `subtasks?: Task[]` be added to the `Task` interface in `task.ts` for future use? OPEN
|
||||||
|
- [ ] Appwrite v23 deprecated positional-param overloads (hints in every store). Should stores be migrated to new object-param style? Low priority — code still works. OPEN
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
|
||||||
|
- ASSUMED: `subtasks` feature in TaskCardComponent was dead/future code — safe to remove template refs
|
||||||
|
- ASSUMED: `no-debugger: 'off'` is fine for devel branch
|
||||||
|
|
||||||
|
## What NOT to Re-Read
|
||||||
|
|
||||||
|
- `docs/archive/handoffs/handoff-2026-03-15-auth-magic-link.md` — archived
|
||||||
|
|
||||||
|
## Next Session
|
||||||
|
|
||||||
|
- Commit all dependency update + build fix changes
|
||||||
|
- Test the app against the dev Appwrite backend (validate v23 API calls work at runtime)
|
||||||
|
- Consider migrating Appwrite calls from deprecated positional-param to object-param style (optional)
|
||||||
|
- Consider adding `subtasks?: Task[]` to `Task` interface if the feature is planned
|
||||||
16
docs/context/archive-rules.md
Normal file
16
docs/context/archive-rules.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Archive Rules
|
||||||
|
|
||||||
|
## Raw File Archival
|
||||||
|
|
||||||
|
After creating a Source Document Summary for any raw file:
|
||||||
|
1. Move the raw file to `docs/archive/`
|
||||||
|
2. Record the move in the source summary's header: `Archived From: [original path]`
|
||||||
|
3. Do not read from `docs/archive/` unless the user explicitly says "go back to the original [filename]"
|
||||||
|
|
||||||
|
## Summary Lifecycle Rules
|
||||||
|
|
||||||
|
1. **Session handoffs expire**: After a new handoff is written, the previous handoff moves to `docs/archive/handoffs/`. Only the latest handoff stays in `docs/summaries/`.
|
||||||
|
2. **Decision records persist**: Decision records (DR-*) stay in `docs/summaries/` permanently — they are institutional memory.
|
||||||
|
3. **Source summaries persist**: Source document summaries stay until the project ends — they replace raw documents.
|
||||||
|
4. **Analysis summaries**: Keep only the latest version. If re-run, the new one replaces the old (archive the old one).
|
||||||
|
5. **Maximum active summaries**: If `docs/summaries/` exceeds 15 files, consolidate older source summaries into a single `project-digest.md` and archive the originals.
|
||||||
23
docs/context/processing-protocol.md
Normal file
23
docs/context/processing-protocol.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Document Processing Protocol
|
||||||
|
|
||||||
|
Use this whenever you need to process multiple documents or large files.
|
||||||
|
|
||||||
|
## For 1-3 Short Documents (< 2K words each)
|
||||||
|
|
||||||
|
Read sequentially. After each document, write a Source Document Summary (Template 1 from `templates/claude-templates.md`) to disk. Then proceed with work using summaries only.
|
||||||
|
|
||||||
|
## For 4+ Documents OR Any Document > 2K Words
|
||||||
|
|
||||||
|
**Step 1:** List all documents with file sizes. Present to user for prioritization.
|
||||||
|
|
||||||
|
**Step 2:** Process each document individually:
|
||||||
|
- Read one document
|
||||||
|
- Extract into Source Document Summary format
|
||||||
|
- Write to `./docs/summaries/source-[filename].md`
|
||||||
|
- Release the document from active consideration before reading the next
|
||||||
|
|
||||||
|
**Step 3:** After all documents are processed, read only the summaries to form your working context.
|
||||||
|
|
||||||
|
**Step 4:** Cross-reference summaries for contradictions or dependencies. Note these explicitly.
|
||||||
|
|
||||||
|
**Step 5:** Proceed with the actual task using summaries as your reference.
|
||||||
18
docs/context/subagent-rules.md
Normal file
18
docs/context/subagent-rules.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Subagent Deployment Rules
|
||||||
|
|
||||||
|
## When to Use Subagent vs. Main Agent
|
||||||
|
|
||||||
|
| Situation | Approach | Why |
|
||||||
|
|-----------|----------|-----|
|
||||||
|
| Reading/analyzing documents | Subagent | Keeps source content out of main context |
|
||||||
|
| Research and competitive analysis | Subagent | Heavy reading, return summary only |
|
||||||
|
| Writing deliverables | Main agent | Needs full decision-making context |
|
||||||
|
| Schema/architecture design | Main agent | Needs holistic project understanding |
|
||||||
|
| Code generation | Subagent | Isolated implementation, return result |
|
||||||
|
| Review and QA | Subagent | Fresh perspective, no bias from writing |
|
||||||
|
|
||||||
|
## Output Requirements
|
||||||
|
|
||||||
|
Subagent output must conform to the Output Contracts in `templates/claude-templates.md`. No free-form prose returns.
|
||||||
|
|
||||||
|
Optimal subagent return size: 1,000-2,000 tokens of structured summary. Longer returns consume main agent context without proportional benefit.
|
||||||
101
docs/summaries/handoff-2026-03-15-build-fixes.md
Normal file
101
docs/summaries/handoff-2026-03-15-build-fixes.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Session Handoff: Build Fixes & Dev Environment
|
||||||
|
**Date:** 2026-03-15
|
||||||
|
**Session Duration:** ~2 hours
|
||||||
|
**Session Focus:** Resolve all TypeScript/ESLint build errors from dependency updates; fix dev server startup
|
||||||
|
**Context Usage at Handoff:** Medium
|
||||||
|
|
||||||
|
## What Was Accomplished
|
||||||
|
|
||||||
|
1. Fixed 30 TypeScript errors (14 files) → build now passes with 0 errors
|
||||||
|
2. Fixed 12 ESLint problems (6 errors, 6 warnings) → 0 remaining
|
||||||
|
3. Fixed `quasar dev` startup error (`FlatESLint is not a constructor`) → downgraded ESLint v10→v9
|
||||||
|
4. Fixed missing Appwrite env vars in `.env.local` → app connects to backend on dev
|
||||||
|
|
||||||
|
## Exact State of Work in Progress
|
||||||
|
|
||||||
|
- **Build**: CLEAN — `yarn quasar build` exits 0, no TS or ESLint errors
|
||||||
|
- **Dev server**: FUNCTIONAL — `quasar dev` starts without errors; ESLint inline checking via `vite-plugin-checker` is restored
|
||||||
|
- **Runtime**: UNTESTED this session — app has not been manually tested against the dev Appwrite backend
|
||||||
|
|
||||||
|
## Decisions Made This Session
|
||||||
|
|
||||||
|
- **`as unknown as Type` for all Appwrite store casts** — CONFIRMED: Appwrite v23 made `DefaultDocument` strict; it no longer overlaps domain types, so the double-cast is required. Applied to: `boat.ts`, `interval.ts`, `intervalTemplate.ts`, `reservation.ts`, `task.ts`
|
||||||
|
- **ESLint downgraded v10.0.3 → v9.39.4** — CONFIRMED: `vite-plugin-checker` v0.12.0 calls `FlatESLint` which was merged back into `ESLint` in v10; v9 preserves the API. Also downgraded `@eslint/js` (v10→v9) and `eslint-plugin-vue` (v10→v9)
|
||||||
|
- **`getWeekdaySkips` removed** — CONFIRMED: removed from `@quasar/quasar-ui-qcalendar` API; `createDayList` now takes `weekdays` array directly as 4th param (previously took `weekdaySkips` computed value)
|
||||||
|
- **`subtasks` removed from TaskCardComponent template** — ASSUMED SAFE: `Task` type has no `subtasks` field; template refs were dead code. See open question.
|
||||||
|
- **`no-debugger: 'off'`** — CONFIRMED: hardcoded because `process` is not available in ESLint globals when linting `.js` files (config file context)
|
||||||
|
- **`.env.local` variable names corrected** — CONFIRMED: file had `VITE_APPWRITE_ENDPOINT` / `VITE_APPWRITE_PROJECT`; `appwrite.ts` reads `VITE_APPWRITE_API_ENDPOINT` / `VITE_APPWRITE_API_PROJECT`
|
||||||
|
|
||||||
|
## Key Numbers Generated or Discovered This Session
|
||||||
|
|
||||||
|
- TypeScript errors at session start: 30 (across 14 files)
|
||||||
|
- ESLint problems at session start: 12 (6 errors, 6 warnings)
|
||||||
|
- TypeScript errors at session end: 0
|
||||||
|
- ESLint problems at session end: 0
|
||||||
|
- ESLint: v10.0.3 → v9.39.4
|
||||||
|
- `@eslint/js`: v10 → v9
|
||||||
|
- `eslint-plugin-vue`: v10 → v9
|
||||||
|
- `register-service-worker`: newly added (was missing from package.json)
|
||||||
|
|
||||||
|
## Conditional Logic Established
|
||||||
|
|
||||||
|
- IF Appwrite SDK returns `DefaultDocument` THEN cast via `as unknown as DomainType` BECAUSE v23 `DefaultDocument` is strict and no longer assignable to domain types that extend `Partial<Models.Document>`
|
||||||
|
- IF `vite-plugin-checker` is v0.12.x THEN ESLint must be v9.x BECAUSE v0.12.x uses `FlatESLint` constructor removed in ESLint v10
|
||||||
|
- IF `createDayList` is called from qcalendar THEN pass `weekdays` array as 4th arg directly BECAUSE `getWeekdaySkips` was removed from the qcalendar public API
|
||||||
|
- IF `.env.local` is updated THEN variable names must match `import.meta.env.VITE_APPWRITE_API_ENDPOINT` / `VITE_APPWRITE_API_PROJECT` as read in `src/boot/appwrite.ts`
|
||||||
|
|
||||||
|
## Files Created or Modified
|
||||||
|
|
||||||
|
| File Path | Action | Description |
|
||||||
|
|-----------|--------|-------------|
|
||||||
|
| `src/stores/boat.ts` | Modified | `as unknown as Boat[]` |
|
||||||
|
| `src/stores/interval.ts` | Modified | `as unknown as Interval` (3 places) |
|
||||||
|
| `src/stores/intervalTemplate.ts` | Modified | Map callback cast + `as unknown as IntervalTemplate` (3 places); `timeTuple` cast |
|
||||||
|
| `src/stores/reservation.ts` | Modified | `as unknown as Reservation` (5 places) |
|
||||||
|
| `src/stores/task.ts` | Modified | `as unknown as Task[]`, `TaskTag[]`, `SkillTag[]`, `Task` (5 places) |
|
||||||
|
| `src/stores/sampledata/schedule.ts` | Modified | `id`→`$id`, `blocks`→`timeTuples`, removed `reservationDate` |
|
||||||
|
| `src/components/boat/BoatPreviewComponent.vue` | Modified | `boat.id` → `boat.$id` |
|
||||||
|
| `src/components/scheduling/boat/BoatScheduleTableComponent.vue` | Modified | `block.id`→`block.$id`; `NodeJS.Timeout`→`ReturnType<typeof setInterval>`; ternary→if/else |
|
||||||
|
| `src/components/scheduling/boat/CalendarHeaderComponent.vue` | Modified | Removed `getWeekdaySkips` import+computed; `createDayList` now passes `weekdays` directly |
|
||||||
|
| `src/components/task/TaskCardComponent.vue` | Modified | Removed `defineProps` explicit import; removed `subtasks` template refs |
|
||||||
|
| `src/components/task/TaskListComponent.vue` | Modified | `task.id` → `task.$id` |
|
||||||
|
| `src/components/task/TaskTableComponent.vue` | Modified | Removed `defineProps` from explicit import |
|
||||||
|
| `src/components/ResourceScheduleViewerComponent.vue` | Modified | Removed `|| undefined`; `catch { }`; removed stale eslint-disable comments |
|
||||||
|
| `src/pages/LoginPage.vue` | Modified | `catch { }` |
|
||||||
|
| `src/pages/schedule/ManageCalendar.vue` | Modified | `block.id` → `block.$id` |
|
||||||
|
| `src/boot/appwrite.ts` | Modified | Removed stale `console.log(API_ENDPOINT)` |
|
||||||
|
| `eslint.config.js` | Modified | `no-debugger` hardcoded to `'off'` |
|
||||||
|
| `quasar.config.ts` | Modified | ESLint checker restored (had been temporarily removed) |
|
||||||
|
| `package.json` / `yarn.lock` | Modified | ESLint v10→v9; `@eslint/js` v10→v9; `eslint-plugin-vue` v10→v9; added `register-service-worker` |
|
||||||
|
| `.env.local` | Modified | Variable names corrected: `VITE_APPWRITE_ENDPOINT`→`VITE_APPWRITE_API_ENDPOINT`, `VITE_APPWRITE_PROJECT`→`VITE_APPWRITE_API_PROJECT`; endpoint URL updated to include `/v1` |
|
||||||
|
| `docs/summaries/handoff-2026-03-15-build-fixes.md` | Created | This file |
|
||||||
|
| `docs/archive/handoffs/handoff-2026-03-15-dependency-updates.md` | Archived | Superseded by this handoff |
|
||||||
|
|
||||||
|
## What the NEXT Session Should Do
|
||||||
|
|
||||||
|
1. **First**: Run `quasar dev` and manually test the login flow against the dev Appwrite backend to validate v23 API calls work at runtime
|
||||||
|
2. **Validate**: Boat listing, reservation creation/cancellation, interval loading — confirm no runtime errors from the v23 positional-param deprecations
|
||||||
|
3. **Commit**: Stage all modified files and commit as `"fix: Resolve build errors from dependency updates"` (single clean commit covering all TS/ESLint/qcalendar/env fixes)
|
||||||
|
4. **Optional**: Migrate Appwrite calls from deprecated positional-param style to object-param style (affects all stores — low priority, they still work)
|
||||||
|
5. **Optional**: Add `subtasks?: Task[]` to `Task` interface in `src/stores/task.ts` if that feature is planned
|
||||||
|
|
||||||
|
## Open Questions Requiring User Input
|
||||||
|
|
||||||
|
- [ ] `task.subtasks` removed from `TaskCardComponent` template — should `subtasks?: Task[]` be added to the `Task` interface for future use, or is subtask support not planned?
|
||||||
|
- [ ] Appwrite v23 deprecated positional-param overloads (hints in every store call). Migrate now or leave for later?
|
||||||
|
|
||||||
|
## Assumptions That Need Validation
|
||||||
|
|
||||||
|
- ASSUMED: Appwrite v23 positional-param API calls behave identically at runtime to v14 — validate by doing a full login + reservation flow against the dev backend
|
||||||
|
- ASSUMED: `subtasks` in `TaskCardComponent` was dead/future code — no user confirmed this
|
||||||
|
|
||||||
|
## What NOT to Re-Read
|
||||||
|
|
||||||
|
- `docs/archive/handoffs/handoff-2026-03-15-dependency-updates.md` — archived; superseded by this file
|
||||||
|
- `docs/archive/handoffs/handoff-2026-03-15-auth-magic-link.md` — archived; auth work complete
|
||||||
|
|
||||||
|
## Files to Load Next Session
|
||||||
|
|
||||||
|
- `src/stores/task.ts` — if adding `subtasks` to Task interface
|
||||||
|
- `src/boot/appwrite.ts` — if migrating to Appwrite v23 object-param style
|
||||||
|
- Any store file (`boat.ts`, `interval.ts`, `reservation.ts`, etc.) — if migrating Appwrite calls
|
||||||
56
eslint.config.js
Normal file
56
eslint.config.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import pluginVue from 'eslint-plugin-vue';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'dist/**',
|
||||||
|
'.quasar/**',
|
||||||
|
'node_modules/**',
|
||||||
|
'src-capacitor/**',
|
||||||
|
'src-cordova/**',
|
||||||
|
'quasar.config.*.temporary.compiled*',
|
||||||
|
'generate-version.cjs',
|
||||||
|
'src-pwa/.eslintrc.js',
|
||||||
|
'**/*.d.ts',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...pluginVue.configs['flat/essential'],
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.vue'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
extraFileExtensions: ['.vue'],
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.es2021,
|
||||||
|
ga: 'readonly',
|
||||||
|
cordova: 'readonly',
|
||||||
|
__statics: 'readonly',
|
||||||
|
__QUASAR_SSR__: 'readonly',
|
||||||
|
__QUASAR_SSR_SERVER__: 'readonly',
|
||||||
|
__QUASAR_SSR_CLIENT__: 'readonly',
|
||||||
|
__QUASAR_SSR_PWA__: 'readonly',
|
||||||
|
process: 'readonly',
|
||||||
|
Capacitor: 'readonly',
|
||||||
|
chrome: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'prefer-promise-reject-errors': 'off',
|
||||||
|
quotes: ['warn', 'single', { avoidEscape: true }],
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'no-debugger': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prettier,
|
||||||
|
);
|
||||||
@@ -9,7 +9,7 @@ try {
|
|||||||
// Create version content
|
// Create version content
|
||||||
const versionContent = `export const APP_VERSION = '${version}';\n`;
|
const versionContent = `export const APP_VERSION = '${version}';\n`;
|
||||||
const versionTxtFilePath = path.resolve(__dirname, './VERSION');
|
const versionTxtFilePath = path.resolve(__dirname, './VERSION');
|
||||||
const versionFilePath = path.resolve(__dirname, 'src/version.js');
|
const versionFilePath = path.resolve(__dirname, 'src/version.ts');
|
||||||
|
|
||||||
// Write version to TXT file
|
// Write version to TXT file
|
||||||
fs.writeFileSync(versionTxtFilePath, version, 'utf8');
|
fs.writeFileSync(versionTxtFilePath, version, 'utf8');
|
||||||
76
package.json
76
package.json
@@ -5,60 +5,64 @@
|
|||||||
"productName": "OYS Borrow a Boat",
|
"productName": "OYS Borrow a Boat",
|
||||||
"author": "Patrick Toal <ptoal@takeflight.ca>",
|
"author": "Patrick Toal <ptoal@takeflight.ca>",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"generate-version": "node generate-version.js",
|
"generate-version": "node generate-version.cjs",
|
||||||
"lint": "eslint --ext .js,.ts,.vue ./",
|
"lint": "eslint .",
|
||||||
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||||
"test": "echo \"No test specified\" && exit 0",
|
"test": "echo \"No test specified\" && exit 0",
|
||||||
"dev": "npm run generate-version && quasar dev -m pwa",
|
"dev": "yarn generate-version && quasar dev -m pwa",
|
||||||
"build": "npm run generate-version && quasar build -m pwa"
|
"build": "yarn generate-version && quasar build -m pwa"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.16.11",
|
"@quasar/extras": "^1.17.0",
|
||||||
"@quasar/quasar-app-extension-qcalendar": "https://github.com/ptoal/quasar-ui-qcalendar/releases/download/v4.0.0-beta.19/app-extension.tgz",
|
"@quasar/quasar-ui-qcalendar": "^4.1.2",
|
||||||
"@quasar/quasar-ui-qcalendar": "https://github.com/ptoal/quasar-ui-qcalendar/releases/download/v4.0.0-beta.19/qcalendar-ui.tgz",
|
"appwrite": "^23.0.0",
|
||||||
"appwrite": "^14.0.1",
|
"axios": "^1.13.6",
|
||||||
"axios": "^1.6.8",
|
|
||||||
"file": "^0.2.2",
|
"file": "^0.2.2",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
|
"register-service-worker": "^1.7.2",
|
||||||
"vue": "3",
|
"vue": "3",
|
||||||
"vue-router": "4",
|
"vue-router": "4",
|
||||||
"vue3-google-login": "^2.0.26"
|
"vue3-google-login": "^2.0.37"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/app-vite": "^1.9.1",
|
"@eslint/js": "^9",
|
||||||
|
"@quasar/app-vite": "^2.4.1",
|
||||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/exec": "^6.0.3",
|
"@semantic-release/exec": "^7.1.0",
|
||||||
"@semantic-release/github": "^10.0.6",
|
"@semantic-release/github": "^12.0.6",
|
||||||
"@semantic-release/npm": "^12.0.1",
|
"@semantic-release/npm": "^13.1.5",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^25.5.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"autoprefixer": "^10.4.27",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
"dotenv": "^17.3.1",
|
||||||
"autoprefixer": "^10.4.2",
|
"eslint": "^9",
|
||||||
"dotenv": "^16.3.1",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint": "^8.10.0",
|
"eslint-plugin-vue": "^9",
|
||||||
"eslint-config-prettier": "^8.1.0",
|
|
||||||
"eslint-plugin-vue": "^9.0.0",
|
|
||||||
"git-commit-info": "^2.0.2",
|
"git-commit-info": "^2.0.2",
|
||||||
"prettier": "^2.5.1",
|
"globals": "^17.4.0",
|
||||||
"quasar": "^2.16.0",
|
"prettier": "^3.8.1",
|
||||||
"semantic-release": "^24.0.0",
|
"quasar": "^2.18.6",
|
||||||
"typescript": "~5.3.0",
|
"semantic-release": "^25.0.3",
|
||||||
"vite-plugin-checker": "^0.6.4",
|
"typescript": "^5.9.3",
|
||||||
"vue-tsc": "^1.8.22",
|
"typescript-eslint": "^8.57.0",
|
||||||
"workbox-build": "^7.0.0",
|
"vite-plugin-checker": "^0.12.0",
|
||||||
"workbox-cacheable-response": "^7.0.0",
|
"vue-eslint-parser": "^10.4.0",
|
||||||
"workbox-core": "^7.0.0",
|
"vue-tsc": "^3.2.5",
|
||||||
"workbox-expiration": "^7.0.0",
|
"workbox-build": "^7.4.0",
|
||||||
"workbox-precaching": "^7.0.0",
|
"workbox-cacheable-response": "^7.4.0",
|
||||||
"workbox-routing": "^7.0.0",
|
"workbox-core": "^7.4.0",
|
||||||
"workbox-strategies": "^7.0.0",
|
"workbox-expiration": "^7.4.0",
|
||||||
|
"workbox-precaching": "^7.4.0",
|
||||||
|
"workbox-routing": "^7.4.0",
|
||||||
|
"workbox-strategies": "^7.4.0",
|
||||||
"yarn": "^1.22.21"
|
"yarn": "^1.22.21"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20 || ^18 || ^16 || ^14.19",
|
"node": "^20 || ^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@4.13.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,10 @@
|
|||||||
/* eslint-env node */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
|
|
||||||
* the ES6 features that are supported by your Node version. https://node.green/
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Configuration for your app
|
// Configuration for your app
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
|
||||||
|
|
||||||
const { configure } = require('quasar/wrappers');
|
import { defineConfig } from '#q-app/wrappers';
|
||||||
|
|
||||||
module.exports = configure(function () {
|
export default defineConfig(function () {
|
||||||
return {
|
return {
|
||||||
eslint: {
|
|
||||||
// fix: true,
|
|
||||||
// include: [],
|
|
||||||
// exclude: [],
|
|
||||||
// rawOptions: {},
|
|
||||||
warnings: true,
|
|
||||||
errors: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
|
// https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
|
||||||
// preFetch: true,
|
// preFetch: true,
|
||||||
|
|
||||||
@@ -79,7 +63,8 @@ module.exports = configure(function () {
|
|||||||
tsconfigPath: 'tsconfig.vue-tsc.json',
|
tsconfigPath: 'tsconfig.vue-tsc.json',
|
||||||
},
|
},
|
||||||
eslint: {
|
eslint: {
|
||||||
lintCommand: 'eslint "./**/*.{js,ts,mjs,cjs,vue}"',
|
lintCommand: 'eslint .',
|
||||||
|
useFlatConfig: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ server: false },
|
{ server: false },
|
||||||
@@ -96,13 +81,13 @@ module.exports = configure(function () {
|
|||||||
// This works around CORS problems when developing locally, using the Appwrite backend
|
// This works around CORS problems when developing locally, using the Appwrite backend
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'https://apidev.bab.toal.ca/',
|
target: 'https://appwrite.toal.ca/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
'/api/v1/realtime': {
|
'/api/v1/realtime': {
|
||||||
target: 'wss://apidev.bab.toal.ca',
|
target: 'wss://appwrite.toal.ca',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
secure: false,
|
secure: false,
|
||||||
@@ -125,9 +110,7 @@ module.exports = configure(function () {
|
|||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
||||||
framework: {
|
framework: {
|
||||||
config: {
|
config: {},
|
||||||
autoImportComponentCase: 'kebab', // or 'kebab' (default) or 'combined'
|
|
||||||
},
|
|
||||||
|
|
||||||
// iconSet: 'material-icons', // Quasar icon set
|
// iconSet: 'material-icons', // Quasar icon set
|
||||||
// lang: 'en-US', // Quasar language pack
|
// lang: 'en-US', // Quasar language pack
|
||||||
@@ -189,7 +172,7 @@ module.exports = configure(function () {
|
|||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
|
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
|
||||||
pwa: {
|
pwa: {
|
||||||
workboxMode: 'generateSW', // or 'injectManifest'
|
workboxMode: 'GenerateSW', // or 'InjectManifest'
|
||||||
injectPwaMetaTags: true,
|
injectPwaMetaTags: true,
|
||||||
swFilename: 'sw.js',
|
swFilename: 'sw.js',
|
||||||
manifestFilename: 'manifest.json',
|
manifestFilename: 'manifest.json',
|
||||||
@@ -240,8 +223,6 @@ module.exports = configure(function () {
|
|||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
|
||||||
bex: {
|
bex: {
|
||||||
contentScripts: ['my-content-script'],
|
|
||||||
|
|
||||||
// extendBexScriptsConf (esbuildConf) {}
|
// extendBexScriptsConf (esbuildConf) {}
|
||||||
// extendBexManifestJson (json) {}
|
// extendBexManifestJson (json) {}
|
||||||
},
|
},
|
||||||
@@ -1,3 +1 @@
|
|||||||
{
|
{}
|
||||||
"@quasar/qcalendar": {}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { boot } from 'quasar/wrappers';
|
import { defineBoot } from '#q-app/wrappers';
|
||||||
import {
|
import {
|
||||||
Client,
|
Client,
|
||||||
Account,
|
Account,
|
||||||
@@ -21,7 +21,7 @@ if (API_ENDPOINT && API_PROJECT) {
|
|||||||
client.setEndpoint(API_ENDPOINT).setProject(API_PROJECT);
|
client.setEndpoint(API_ENDPOINT).setProject(API_PROJECT);
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
'Must configure VITE_APPWRITE_API_ENDPOINT and VITE_APPWRITE_API_PROJECT'
|
'Must configure VITE_APPWRITE_API_ENDPOINT and VITE_APPWRITE_API_PROJECT',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,13 +43,8 @@ type AppwriteIDConfig = {
|
|||||||
|
|
||||||
let AppwriteIds = <AppwriteIDConfig>{};
|
let AppwriteIds = <AppwriteIDConfig>{};
|
||||||
|
|
||||||
console.log(API_ENDPOINT);
|
|
||||||
if (
|
|
||||||
API_ENDPOINT === 'https://apidev.bab.toal.ca/v1' ||
|
|
||||||
API_ENDPOINT === 'http://localhost:4000/api/v1'
|
|
||||||
) {
|
|
||||||
AppwriteIds = {
|
AppwriteIds = {
|
||||||
databaseId: '65ee1cbf9c2493faf15f',
|
databaseId: 'bab_prod',
|
||||||
collection: {
|
collection: {
|
||||||
boat: 'boat',
|
boat: 'boat',
|
||||||
reservation: 'reservation',
|
reservation: 'reservation',
|
||||||
@@ -63,23 +58,6 @@ if (
|
|||||||
userinfo: 'userinfo',
|
userinfo: 'userinfo',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (API_ENDPOINT === 'https://appwrite.oys.undock.ca/v1') {
|
|
||||||
AppwriteIds = {
|
|
||||||
databaseId: 'bab_prod',
|
|
||||||
collection: {
|
|
||||||
boat: 'boat',
|
|
||||||
reservation: 'reservation',
|
|
||||||
skillTags: 'skillTags',
|
|
||||||
task: 'task',
|
|
||||||
taskTags: 'taskTags',
|
|
||||||
interval: 'interval',
|
|
||||||
intervalTemplate: 'intervalTemplate',
|
|
||||||
},
|
|
||||||
function: {
|
|
||||||
userinfo: '664038294b5473ef0c8d',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = new Account(client);
|
const account = new Account(client);
|
||||||
const databases = new Databases(client);
|
const databases = new Databases(client);
|
||||||
@@ -88,7 +66,7 @@ const teams = new Teams(client);
|
|||||||
|
|
||||||
let appRouter: Router;
|
let appRouter: Router;
|
||||||
|
|
||||||
export default boot(async ({ router }) => {
|
export default defineBoot(async ({ router }) => {
|
||||||
// Initialize store
|
// Initialize store
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
await authStore.init();
|
await authStore.init();
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-btn
|
|
||||||
@click="auth.discordLogin()"
|
|
||||||
style="width: 300px">
|
|
||||||
<q-avatar
|
|
||||||
left
|
|
||||||
size="sm"
|
|
||||||
class="q-ma-xs">
|
|
||||||
<img
|
|
||||||
src="https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/636e0a6a49cf127bf92de1e2_icon_clyde_blurple_RGB.png" />
|
|
||||||
</q-avatar>
|
|
||||||
Login with Discord
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useAuthStore } from 'src/stores/auth';
|
|
||||||
const auth = useAuthStore();
|
|
||||||
</script>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-btn
|
|
||||||
@click="auth.googleLogin()"
|
|
||||||
style="width: 300px">
|
|
||||||
<q-avatar
|
|
||||||
left
|
|
||||||
class="q-ma-xs"
|
|
||||||
size="sm">
|
|
||||||
<img
|
|
||||||
src="https://lh3.googleusercontent.com/COxitqgJr1sJnIDe8-jiKhxDx1FrYbtRHKJ9z_hELisAlapwE9LUPh6fcXIfb5vwpbMl4xl9H9TRFPc5NOO8Sb3VSgIBrfRYvW6cUA" />
|
|
||||||
</q-avatar>
|
|
||||||
<div>Login with Google</div>
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useAuthStore } from 'src/stores/auth';
|
|
||||||
const auth = useAuthStore();
|
|
||||||
</script>
|
|
||||||
@@ -49,6 +49,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</q-list>
|
</q-list>
|
||||||
</template>
|
</template>
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
@click="showAbout()">
|
||||||
|
<q-item-section avatar><q-icon name="info" /></q-item-section>
|
||||||
|
<q-item-section>About</q-item-section>
|
||||||
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
clickable
|
clickable
|
||||||
v-ripple
|
v-ripple
|
||||||
@@ -63,8 +70,18 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import { Dialog } from 'quasar';
|
||||||
import { enabledLinks } from 'src/router/navlinks.js';
|
import { enabledLinks } from 'src/router/navlinks.js';
|
||||||
import { logout } from 'boot/appwrite';
|
import { logout } from 'src/boot/appwrite';
|
||||||
|
import { APP_VERSION } from 'src/version';
|
||||||
|
|
||||||
|
function showAbout() {
|
||||||
|
Dialog.create({
|
||||||
|
title: 'OYS Borrow a Boat',
|
||||||
|
message: `Version ${APP_VERSION}<br>Manage a Borrow a Boat program for a Yacht Club.<br><br>© Oakville Yacht Squadron`,
|
||||||
|
html: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
defineProps(['drawer']);
|
defineProps(['drawer']);
|
||||||
defineEmits(['drawer-toggle']);
|
defineEmits(['drawer-toggle']);
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import {
|
import {
|
||||||
QCalendarResource,
|
QCalendarResource,
|
||||||
|
QCalendarMonth,
|
||||||
TimestampOrNull,
|
TimestampOrNull,
|
||||||
today,
|
today,
|
||||||
parseTimestamp,
|
parseTimestamp,
|
||||||
@@ -169,11 +170,11 @@ const disabledBefore = computed(() => {
|
|||||||
|
|
||||||
function monthFormatter() {
|
function monthFormatter() {
|
||||||
try {
|
try {
|
||||||
return new Intl.DateTimeFormat('en-CA' || undefined, {
|
return new Intl.DateTimeFormat('en-CA', {
|
||||||
month: 'long',
|
month: 'long',
|
||||||
timeZone: 'UTC',
|
timeZone: 'UTC',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,16 +234,10 @@ function onClickTime(data: EventData) {
|
|||||||
function onUpdateDuration(value: EventData) {
|
function onUpdateDuration(value: EventData) {
|
||||||
emit('onUpdateDuration', value);
|
emit('onUpdateDuration', value);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
const onClickInterval = () => {};
|
const onClickInterval = () => {};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
const onClickHeadResources = () => {};
|
const onClickHeadResources = () => {};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
const onClickResource = () => {};
|
const onClickResource = () => {};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
const onResourceExpanded = () => {};
|
const onResourceExpanded = () => {};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
const onMoved = () => {};
|
const onMoved = () => {};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
const onChange = () => {};
|
const onChange = () => {};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,28 +7,20 @@
|
|||||||
round
|
round
|
||||||
icon="menu"
|
icon="menu"
|
||||||
aria-label="Menu"
|
aria-label="Menu"
|
||||||
@click="toggleLeftDrawer" />
|
@click="$emit('drawer-toggle')" />
|
||||||
|
|
||||||
<q-toolbar-title>{{ pageTitle }}</q-toolbar-title>
|
<q-toolbar-title>{{ pageTitle }}</q-toolbar-title>
|
||||||
<q-space />
|
<q-space />
|
||||||
<div>v{{ APP_VERSION }}</div>
|
<div>v{{ APP_VERSION }}</div>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
<LeftDrawer
|
|
||||||
:drawer="leftDrawerOpen"
|
|
||||||
@drawer-toggle="toggleLeftDrawer" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
|
||||||
import LeftDrawer from 'components/LeftDrawer.vue';
|
|
||||||
import { APP_VERSION } from 'src/version';
|
import { APP_VERSION } from 'src/version';
|
||||||
|
|
||||||
const leftDrawerOpen = ref(false);
|
|
||||||
function toggleLeftDrawer() {
|
|
||||||
leftDrawerOpen.value = !leftDrawerOpen.value;
|
|
||||||
}
|
|
||||||
defineProps({
|
defineProps({
|
||||||
pageTitle: String,
|
pageTitle: String,
|
||||||
});
|
});
|
||||||
|
defineEmits(['drawer-toggle']);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
class="row">
|
class="row">
|
||||||
<q-card
|
<q-card
|
||||||
v-for="boat in boats"
|
v-for="boat in boats"
|
||||||
:key="boat.id"
|
:key="boat.$id"
|
||||||
class="q-ma-sm col-xs-12 col-sm-6 col-xl-3">
|
class="q-ma-sm col-xs-12 col-sm-6 col-xl-3">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-img
|
<q-img
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
scope.timeDurationHeight
|
scope.timeDurationHeight
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:id="block.id"
|
:id="block.$id"
|
||||||
@click="selectBlock($event, scope, block)">
|
@click="selectBlock($event, scope, block)">
|
||||||
{{ boats[scope.columnIndex].name }}
|
{{ boats[scope.columnIndex].name }}
|
||||||
<br />
|
<br />
|
||||||
@@ -113,7 +113,7 @@ const selectedDate = ref(today());
|
|||||||
const { getAvailableIntervals } = useIntervalStore();
|
const { getAvailableIntervals } = useIntervalStore();
|
||||||
const calendar = ref<QCalendarDay | null>(null);
|
const calendar = ref<QCalendarDay | null>(null);
|
||||||
const now = ref(new Date());
|
const now = ref(new Date());
|
||||||
let intervalId: string | number | NodeJS.Timeout | undefined;
|
let intervalId: ReturnType<typeof setInterval> | undefined;
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await useBoatStore().fetchBoats();
|
await useBoatStore().fetchBoats();
|
||||||
@@ -125,8 +125,12 @@ onMounted(async () => {
|
|||||||
|
|
||||||
onUnmounted(() => clearInterval(intervalId));
|
onUnmounted(() => clearInterval(intervalId));
|
||||||
|
|
||||||
function handleSwipe({ ...event }) {
|
function handleSwipe({ direction }: { direction: string }) {
|
||||||
event.direction === 'right' ? calendar.value?.prev() : calendar.value?.next();
|
if (direction === 'right') {
|
||||||
|
calendar.value?.prev();
|
||||||
|
} else {
|
||||||
|
calendar.value?.next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function reservationStyles(
|
function reservationStyles(
|
||||||
reservation: Reservation,
|
reservation: Reservation,
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import {
|
|||||||
createNativeLocaleFormatter,
|
createNativeLocaleFormatter,
|
||||||
getEndOfWeek,
|
getEndOfWeek,
|
||||||
getStartOfWeek,
|
getStartOfWeek,
|
||||||
getWeekdaySkips,
|
|
||||||
parseTimestamp,
|
parseTimestamp,
|
||||||
today,
|
today,
|
||||||
} from '@quasar/quasar-ui-qcalendar';
|
} from '@quasar/quasar-ui-qcalendar';
|
||||||
@@ -63,10 +62,6 @@ const weekdays = reactive([1, 2, 3, 4, 5, 6, 0]),
|
|||||||
dayFormatter = dayFormatterFunc(),
|
dayFormatter = dayFormatterFunc(),
|
||||||
weekdayFormatter = weekdayFormatterFunc();
|
weekdayFormatter = weekdayFormatterFunc();
|
||||||
|
|
||||||
const weekdaySkips = computed(() => {
|
|
||||||
return getWeekdaySkips(weekdays);
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsedStart = computed(() =>
|
const parsedStart = computed(() =>
|
||||||
getStartOfWeek(
|
getStartOfWeek(
|
||||||
parseTimestamp(selectedDate.value || today()) as Timestamp,
|
parseTimestamp(selectedDate.value || today()) as Timestamp,
|
||||||
@@ -93,7 +88,7 @@ const days = computed(() => {
|
|||||||
parsedStart.value,
|
parsedStart.value,
|
||||||
parsedEnd.value,
|
parsedEnd.value,
|
||||||
today2.value as Timestamp,
|
today2.value as Timestamp,
|
||||||
weekdaySkips.value
|
weekdays
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -5,19 +5,10 @@
|
|||||||
<q-item-label caption lines="2">{{ task.description }} </q-item-label>
|
<q-item-label caption lines="2">{{ task.description }} </q-item-label>
|
||||||
<q-item-label caption>Due: {{ task.due_date }}</q-item-label>
|
<q-item-label caption>Due: {{ task.due_date }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-expansion-item
|
|
||||||
v-if="task.subtasks && task.subtasks.length"
|
|
||||||
expand-separator
|
|
||||||
label="Subtasks"
|
|
||||||
default-opened
|
|
||||||
>
|
|
||||||
<TaskListComponent :tasks="task.subtasks" />
|
|
||||||
</q-expansion-item>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps } from 'vue';
|
|
||||||
import type { Task } from 'src/stores/task';
|
import type { Task } from 'src/stores/task';
|
||||||
|
|
||||||
defineProps<{ task: Task }>();
|
defineProps<{ task: Task }>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md" style="max-width: 350px">
|
<div class="q-pa-md" style="max-width: 350px">
|
||||||
<q-list>
|
<q-list>
|
||||||
<div v-for="task in tasks" :key="task.id">
|
<div v-for="task in tasks" :key="task.$id">
|
||||||
<TaskCardComponent :task="task" />
|
<TaskCardComponent :task="task" />
|
||||||
</div>
|
</div>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
|||||||
@@ -212,7 +212,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineProps, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useTaskStore, Task, SkillTag, TaskTag } from 'src/stores/task';
|
import { useTaskStore, Task, SkillTag, TaskTag } from 'src/stores/task';
|
||||||
import { QTableProps, date, useQuasar } from 'quasar';
|
import { QTableProps, date, useQuasar } from 'quasar';
|
||||||
import { Boat, useBoatStore } from 'src/stores/boat';
|
import { Boat, useBoatStore } from 'src/stores/boat';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// app global css in SASS form
|
// app global css in SASS form
|
||||||
|
@import '@quasar/quasar-ui-qcalendar/dist/index.css'
|
||||||
.mobile-card
|
.mobile-card
|
||||||
width: 100%
|
width: 100%
|
||||||
max-width: 450px
|
max-width: 450px
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-layout view="hHh Lpr fFf">
|
<q-layout view="hHh Lpr fFf">
|
||||||
|
<q-header elevated>
|
||||||
|
<q-toolbar>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
icon="menu"
|
||||||
|
aria-label="Menu"
|
||||||
|
@click="toggleLeftDrawer" />
|
||||||
|
<q-toolbar-title>{{ route.meta.title as string }}</q-toolbar-title>
|
||||||
|
<q-space />
|
||||||
|
<div>v{{ APP_VERSION }}</div>
|
||||||
|
</q-toolbar>
|
||||||
|
</q-header>
|
||||||
|
<LeftDrawer
|
||||||
|
:drawer="leftDrawerOpen"
|
||||||
|
@drawer-toggle="toggleLeftDrawer" />
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
<router-view />
|
<router-view />
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
@@ -10,10 +27,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import BottomNavComponent from 'src/components/BottomNavComponent.vue';
|
import BottomNavComponent from 'src/components/BottomNavComponent.vue';
|
||||||
|
import LeftDrawer from 'src/components/LeftDrawer.vue';
|
||||||
|
import { APP_VERSION } from 'src/version';
|
||||||
|
|
||||||
const q = useQuasar();
|
const q = useQuasar();
|
||||||
|
const route = useRoute();
|
||||||
|
const leftDrawerOpen = ref(false);
|
||||||
|
function toggleLeftDrawer() {
|
||||||
|
leftDrawerOpen.value = !leftDrawerOpen.value;
|
||||||
|
}
|
||||||
// q.fullscreen.request();
|
// q.fullscreen.request();
|
||||||
q.addressbarColor.set('#14539a');
|
q.addressbarColor.set('#14539a');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<ToolbarComponent />
|
|
||||||
<q-page class="row justify-center">
|
<q-page class="row justify-center">
|
||||||
<q-img alt="OYS Logo" src="~assets/oysqn_logo.png" fit="scale-down" />
|
<q-img alt="OYS Logo" src="~assets/oysqn_logo.png" fit="scale-down" />
|
||||||
<q-list class="full-width mobile-only">
|
<q-list class="full-width mobile-only">
|
||||||
@@ -24,5 +23,4 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { enabledLinks } from 'src/router/navlinks.js';
|
import { enabledLinks } from 'src/router/navlinks.js';
|
||||||
import ToolbarComponent from 'components/ToolbarComponent.vue';
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -34,24 +34,20 @@
|
|||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<div class="row justify-center q-ma-sm">
|
<div class="row justify-center q-ma-sm">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
v-if="!userId"
|
||||||
type="button"
|
type="button"
|
||||||
@click="doTokenLogin"
|
@click="sendMagicLink"
|
||||||
color="primary"
|
color="secondary"
|
||||||
label="Login with E-mail"
|
label="Send Magic Link"
|
||||||
style="width: 300px" />
|
style="width: 300px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-center q-ma-sm">
|
<div class="row justify-center q-ma-sm">
|
||||||
<GoogleOauthComponent />
|
|
||||||
</div>
|
|
||||||
<div class="row justify-center q-ma-sm">
|
|
||||||
<DiscordOauthComponent />
|
|
||||||
</div>
|
|
||||||
<div class="row justify-center">
|
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
type="button"
|
||||||
color="secondary"
|
@click="doTokenLogin"
|
||||||
to="/pwreset"
|
color="primary"
|
||||||
label="Forgot Password?" />
|
:label="userId ? 'Login' : 'Send Code'"
|
||||||
|
style="width: 300px" />
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
@@ -76,21 +72,54 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue';
|
|
||||||
import DiscordOauthComponent from 'src/components/DiscordOauthComponent.vue';
|
|
||||||
import { Dialog, Notify } from 'quasar';
|
import { Dialog, Notify } from 'quasar';
|
||||||
import { useAuthStore } from 'src/stores/auth';
|
import { useAuthStore } from 'src/stores/auth';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { AppwriteException } from 'appwrite';
|
import { AppwriteException } from 'appwrite';
|
||||||
import { APP_VERSION } from 'src/version.js';
|
|
||||||
|
|
||||||
const email = ref('');
|
const email = ref('');
|
||||||
const token = ref('');
|
const token = ref('');
|
||||||
const userId = ref();
|
const userId = ref();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
console.log('version:' + APP_VERSION);
|
onMounted(async () => {
|
||||||
|
const query = router.currentRoute.value.query;
|
||||||
|
if (query.userId && query.secret) {
|
||||||
|
const notification = Notify.create({
|
||||||
|
type: 'primary',
|
||||||
|
position: 'top',
|
||||||
|
spinner: true,
|
||||||
|
message: 'Logging you in...',
|
||||||
|
timeout: 8000,
|
||||||
|
group: false,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await authStore.magicURLLogin(query.userId as string, query.secret as string);
|
||||||
|
notification({ type: 'positive', message: 'Logged in!', timeout: 2000, spinner: false, icon: 'check_circle' });
|
||||||
|
router.replace({ name: 'index' });
|
||||||
|
} catch (error: unknown) {
|
||||||
|
notification({ type: 'negative', message: 'Magic link login failed.', timeout: 3000, spinner: false });
|
||||||
|
if (error instanceof AppwriteException) {
|
||||||
|
Dialog.create({ title: 'Login Error', message: error.message, persistent: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendMagicLink = async () => {
|
||||||
|
if (!email.value) {
|
||||||
|
Dialog.create({ message: 'Please enter your e-mail address.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await authStore.createMagicURLSession(email.value);
|
||||||
|
Dialog.create({ message: 'Check your e-mail for a magic login link.' });
|
||||||
|
} catch {
|
||||||
|
Dialog.create({ message: 'An error occurred. Please ask for help in Discord.' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const doTokenLogin = async () => {
|
const doTokenLogin = async () => {
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
@@ -99,7 +128,7 @@ const doTokenLogin = async () => {
|
|||||||
const sessionToken = await authStore.createTokenSession(email.value);
|
const sessionToken = await authStore.createTokenSession(email.value);
|
||||||
userId.value = sessionToken.userId;
|
userId.value = sessionToken.userId;
|
||||||
Dialog.create({ message: 'Check your e-mail for your login code.' });
|
Dialog.create({ message: 'Check your e-mail for your login code.' });
|
||||||
} catch (e) {
|
} catch {
|
||||||
Dialog.create({
|
Dialog.create({
|
||||||
message: 'An error occurred. Please ask for help in Discord',
|
message: 'An error occurred. Please ask for help in Discord',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<toolbar-component pageTitle="Member Profile" />
|
|
||||||
<q-page
|
<q-page
|
||||||
padding
|
padding
|
||||||
class="row">
|
class="row">
|
||||||
@@ -76,7 +75,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ToolbarComponent from 'src/components/ToolbarComponent.vue';
|
|
||||||
import { useAuthStore } from 'src/stores/auth';
|
import { useAuthStore } from 'src/stores/auth';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ import { useAuthStore } from 'src/stores/auth';
|
|||||||
import NewPasswordComponent from 'src/components/NewPasswordComponent.vue';
|
import NewPasswordComponent from 'src/components/NewPasswordComponent.vue';
|
||||||
import { Dialog } from 'quasar';
|
import { Dialog } from 'quasar';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { APP_VERSION } from 'src/version.js';
|
import { APP_VERSION } from 'src/version';
|
||||||
|
|
||||||
const email = ref('');
|
const email = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<q-page>
|
||||||
<BoatReservationComponent v-model="newReservation" />
|
<BoatReservationComponent v-model="newReservation" />
|
||||||
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
:transparent="interval.user != undefined"
|
:transparent="interval.user != undefined"
|
||||||
:color="interval.user ? 'secondary' : 'primary'"
|
:color="interval.user ? 'secondary' : 'primary'"
|
||||||
:outline="!interval.user"
|
:outline="!interval.user"
|
||||||
:id="interval.id">
|
:id="interval.$id">
|
||||||
{{
|
{{
|
||||||
interval.user
|
interval.user
|
||||||
? useAuthStore().getUserNameById(interval.user)
|
? useAuthStore().getUserNameById(interval.user)
|
||||||
@@ -132,7 +132,11 @@ const createReservationFromInterval = (interval: Interval | Reservation) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function handleSwipe({ ...event }) {
|
function handleSwipe({ ...event }) {
|
||||||
event.direction === 'right' ? calendar.value?.prev() : calendar.value?.next();
|
if (event.direction === 'right') {
|
||||||
|
calendar.value?.prev();
|
||||||
|
} else {
|
||||||
|
calendar.value?.next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const boatReservationEvents = (
|
const boatReservationEvents = (
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<q-page>
|
||||||
<q-tabs
|
<q-tabs
|
||||||
v-model="tab"
|
v-model="tab"
|
||||||
inline-label
|
inline-label
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useReservationStore } from 'src/stores/reservation';
|
import { useReservationStore } from 'src/stores/reservation';
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<template
|
<template
|
||||||
v-for="block in sortedIntervals(scope.timestamp, scope.resource)
|
v-for="block in sortedIntervals(scope.timestamp, scope.resource)
|
||||||
.value"
|
.value"
|
||||||
:key="block.id">
|
:key="block.$id">
|
||||||
<q-chip class="cursor-pointer">
|
<q-chip class="cursor-pointer">
|
||||||
{{ date.formatDate(block.start, 'HH:mm') }} -
|
{{ date.formatDate(block.start, 'HH:mm') }} -
|
||||||
{{ date.formatDate(block.end, 'HH:mm') }}
|
{{ date.formatDate(block.end, 'HH:mm') }}
|
||||||
@@ -126,7 +126,6 @@
|
|||||||
:model-value="template" />
|
:model-value="template" />
|
||||||
</q-list>
|
</q-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<q-dialog v-model="alert">
|
<q-dialog v-model="alert">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
@@ -151,6 +150,7 @@
|
|||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -208,8 +208,8 @@ const filteredIntervals = (date: Timestamp, boat: Boat) => {
|
|||||||
const sortedIntervals = (date: Timestamp, boat: Boat) => {
|
const sortedIntervals = (date: Timestamp, boat: Boat) => {
|
||||||
return computed(() =>
|
return computed(() =>
|
||||||
filteredIntervals(date, boat).value.sort(
|
filteredIntervals(date, boat).value.sort(
|
||||||
(a, b) => Date.parse(a.start) - Date.parse(b.start)
|
(a, b) => Date.parse(a.start) - Date.parse(b.start),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -235,14 +235,14 @@ function getIntervals(date: Timestamp, boat: Boat) {
|
|||||||
function intervalsFromTemplate(
|
function intervalsFromTemplate(
|
||||||
boat: Boat,
|
boat: Boat,
|
||||||
templateId: string,
|
templateId: string,
|
||||||
date: string
|
date: string,
|
||||||
): Interval[] {
|
): Interval[] {
|
||||||
const template = intervalTemplateStore
|
const template = intervalTemplateStore
|
||||||
.getIntervalTemplates()
|
.getIntervalTemplates()
|
||||||
.value.find((t) => t.$id === templateId);
|
.value.find((t) => t.$id === templateId);
|
||||||
return template
|
return template
|
||||||
? template.timeTuples.map((timeTuple: TimeTuple) =>
|
? template.timeTuples.map((timeTuple: TimeTuple) =>
|
||||||
buildInterval(boat, timeTuple, date)
|
buildInterval(boat, timeTuple, date),
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ function onDrop(
|
|||||||
//TODO: Move all overlap checking to the store. This is too messy right now.
|
//TODO: Move all overlap checking to the store. This is too messy right now.
|
||||||
e: DragEvent,
|
e: DragEvent,
|
||||||
type: string,
|
type: string,
|
||||||
scope: { resource: Boat; timestamp: Timestamp }
|
scope: { resource: Boat; timestamp: Timestamp },
|
||||||
) {
|
) {
|
||||||
if (e.target instanceof HTMLDivElement)
|
if (e.target instanceof HTMLDivElement)
|
||||||
e.target.classList.remove('bg-secondary');
|
e.target.classList.remove('bg-secondary');
|
||||||
@@ -294,9 +294,9 @@ function onDrop(
|
|||||||
.map((boat) =>
|
.map((boat) =>
|
||||||
intervalsOverlapped(
|
intervalsOverlapped(
|
||||||
existingIntervals.value.concat(
|
existingIntervals.value.concat(
|
||||||
intervalsFromTemplate(boat, templateId, date)
|
intervalsFromTemplate(boat, templateId, date),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.flat(1);
|
.flat(1);
|
||||||
if (overlapped.value.length === 0) {
|
if (overlapped.value.length === 0) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<q-page>
|
||||||
<BoatReservationComponent v-model="reservation" />
|
<BoatReservationComponent v-model="reservation" />
|
||||||
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<toolbar-component pageTitle="Schedule" />
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ToolbarComponent from 'src/components/ToolbarComponent.vue';
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
2
src/quasar.d.ts
vendored
2
src/quasar.d.ts
vendored
@@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
// Forces TS to apply `@quasar/app-vite` augmentations of `quasar` package
|
// Forces TS to apply `@quasar/app-vite` augmentations of `quasar` package
|
||||||
// Removing this would break `quasar/wrappers` imports as those typings are declared
|
// Removing this would break `quasar/wrappers` imports as those typings are declared
|
||||||
// into `@quasar/app-vite`
|
// into `@quasar/app-vite`
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { route } from 'quasar/wrappers';
|
import { defineRouter } from '#q-app/wrappers';
|
||||||
import {
|
import {
|
||||||
createMemoryHistory,
|
createMemoryHistory,
|
||||||
createRouter,
|
createRouter,
|
||||||
@@ -22,7 +22,7 @@ const publicRoutes = routes
|
|||||||
* with the Router instance.
|
* with the Router instance.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default route(function (/* { store, ssrContext } */) {
|
export default defineRouter(function (/* { store, ssrContext } */) {
|
||||||
const createHistory = process.env.SERVER
|
const createHistory = process.env.SERVER
|
||||||
? createMemoryHistory
|
? createMemoryHistory
|
||||||
: process.env.VUE_ROUTER_MODE === 'history'
|
: process.env.VUE_ROUTER_MODE === 'history'
|
||||||
|
|||||||
@@ -13,16 +13,19 @@ const routes: RouteRecordRaw[] = [
|
|||||||
// component: () => import('pages/IndexPage.vue'),
|
// component: () => import('pages/IndexPage.vue'),
|
||||||
component: () => import('src/pages/IndexPage.vue'),
|
component: () => import('src/pages/IndexPage.vue'),
|
||||||
name: 'index',
|
name: 'index',
|
||||||
|
meta: { title: 'OYS Borrow a Boat' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/boat',
|
path: '/boat',
|
||||||
component: () => import('src/pages/BoatPage.vue'),
|
component: () => import('src/pages/BoatPage.vue'),
|
||||||
name: 'boat',
|
name: 'boat',
|
||||||
|
meta: { title: 'Boats' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/schedule',
|
path: '/schedule',
|
||||||
component: () => import('pages/schedule/SchedulePageView.vue'),
|
component: () => import('pages/schedule/SchedulePageView.vue'),
|
||||||
name: 'schedule',
|
name: 'schedule',
|
||||||
|
meta: { title: 'Schedule' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -64,10 +67,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/certification',
|
path: '/certification',
|
||||||
component: () => import('src/pages/CertificationPage.vue'),
|
component: () => import('src/pages/CertificationPage.vue'),
|
||||||
name: 'certification',
|
name: 'certification',
|
||||||
|
meta: { title: 'Certifications' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/task',
|
path: '/task',
|
||||||
name: 'task',
|
name: 'task',
|
||||||
|
meta: { title: 'Tasks' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -85,16 +90,19 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/checklist',
|
path: '/checklist',
|
||||||
component: () => import('pages/ChecklistPage.vue'),
|
component: () => import('pages/ChecklistPage.vue'),
|
||||||
name: 'checklist',
|
name: 'checklist',
|
||||||
|
meta: { title: 'Checklist' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
component: () => import('src/pages/ProfilePage.vue'),
|
component: () => import('src/pages/ProfilePage.vue'),
|
||||||
name: 'profile',
|
name: 'profile',
|
||||||
|
meta: { title: 'Member Profile' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/reference',
|
path: '/reference',
|
||||||
component: () => import('src/pages/reference/ReferencePage.vue'),
|
component: () => import('src/pages/reference/ReferencePage.vue'),
|
||||||
name: 'reference',
|
name: 'reference',
|
||||||
|
meta: { title: 'Reference' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -103,7 +111,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'reference-index',
|
name: 'reference-index',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/reference/:id/view',
|
path: 'reference/:id/view',
|
||||||
component: () =>
|
component: () =>
|
||||||
import('src/pages/reference/ReferenceItemPage.vue'),
|
import('src/pages/reference/ReferenceItemPage.vue'),
|
||||||
},
|
},
|
||||||
@@ -117,12 +125,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
meta: { requiredRoles: ['admin'] },
|
meta: { requiredRoles: ['admin'] },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/user',
|
path: 'user',
|
||||||
component: () => import('pages/admin/UserAdminPage.vue'),
|
component: () => import('pages/admin/UserAdminPage.vue'),
|
||||||
name: 'useradmin',
|
name: 'useradmin',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/boat',
|
path: 'boat',
|
||||||
component: () => import('pages/admin/BoatAdminPage.vue'),
|
component: () => import('pages/admin/BoatAdminPage.vue'),
|
||||||
name: 'boatadmin',
|
name: 'boatadmin',
|
||||||
},
|
},
|
||||||
@@ -144,14 +152,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
publicRoute: true,
|
publicRoute: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/login',
|
|
||||||
component: () => import('pages/LoginPage.vue'),
|
|
||||||
name: 'login',
|
|
||||||
meta: {
|
|
||||||
publicRoute: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/terms-of-service',
|
path: '/terms-of-service',
|
||||||
component: () => import('pages/TermsOfServicePage.vue'),
|
component: () => import('pages/TermsOfServicePage.vue'),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ID, account, functions, teams } from 'boot/appwrite';
|
import { ID, account, functions, teams } from 'boot/appwrite';
|
||||||
import { ExecutionMethod, OAuthProvider, type Models } from 'appwrite';
|
import { ExecutionMethod, type Models } from 'appwrite';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useBoatStore } from './boat';
|
import { useBoatStore } from './boat';
|
||||||
import { useReservationStore } from './reservation';
|
import { useReservationStore } from './reservation';
|
||||||
@@ -49,22 +49,12 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
return await account.createEmailToken(ID.unique(), email);
|
return await account.createEmailToken(ID.unique(), email);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function googleLogin() {
|
async function createMagicURLSession(email: string) {
|
||||||
await account.createOAuth2Session(
|
return await account.createMagicURLToken(
|
||||||
OAuthProvider.Google,
|
ID.unique(),
|
||||||
'https://oys.undock.ca',
|
email,
|
||||||
'https://oys.undock.ca/login'
|
window.location.origin + '/login'
|
||||||
);
|
);
|
||||||
await init();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function discordLogin() {
|
|
||||||
await account.createOAuth2Session(
|
|
||||||
OAuthProvider.Discord,
|
|
||||||
'https://oys.undock.ca',
|
|
||||||
'https://oys.undock.ca/login'
|
|
||||||
);
|
|
||||||
await init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tokenLogin(userId: string, token: string) {
|
async function tokenLogin(userId: string, token: string) {
|
||||||
@@ -72,6 +62,11 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
await init();
|
await init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function magicURLLogin(userId: string, secret: string) {
|
||||||
|
await account.updateMagicURLSession(userId, secret);
|
||||||
|
await init();
|
||||||
|
}
|
||||||
|
|
||||||
function getUserNameById(id: string | undefined | null): string {
|
function getUserNameById(id: string | undefined | null): string {
|
||||||
if (!id) return 'No User';
|
if (!id) return 'No User';
|
||||||
try {
|
try {
|
||||||
@@ -100,7 +95,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
return account.deleteSession('current').then((currentUser.value = null));
|
return account.deleteSession('current').then(() => { currentUser.value = null; });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateName(name: string) {
|
async function updateName(name: string) {
|
||||||
@@ -115,10 +110,10 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
register,
|
register,
|
||||||
updateName,
|
updateName,
|
||||||
login,
|
login,
|
||||||
googleLogin,
|
|
||||||
discordLogin,
|
|
||||||
createTokenSession,
|
createTokenSession,
|
||||||
|
createMagicURLSession,
|
||||||
tokenLogin,
|
tokenLogin,
|
||||||
|
magicURLLogin,
|
||||||
logout,
|
logout,
|
||||||
init,
|
init,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const useBoatStore = defineStore('boat', () => {
|
|||||||
AppwriteIds.databaseId,
|
AppwriteIds.databaseId,
|
||||||
AppwriteIds.collection.boat
|
AppwriteIds.collection.boat
|
||||||
);
|
);
|
||||||
boats.value = response.documents as Boat[];
|
boats.value = response.documents as unknown as Boat[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch boats', error);
|
console.error('Failed to fetch boats', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,5 @@
|
|||||||
import { store } from 'quasar/wrappers';
|
import { defineStore } from '#q-app/wrappers';
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import { Router } from 'vue-router';
|
|
||||||
|
|
||||||
/*
|
|
||||||
* When adding new properties to stores, you should also
|
|
||||||
* extend the `PiniaCustomProperties` interface.
|
|
||||||
* @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties
|
|
||||||
*/
|
|
||||||
declare module 'pinia' {
|
|
||||||
export interface PiniaCustomProperties {
|
|
||||||
readonly router: Router;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If not building with SSR mode, you can
|
* If not building with SSR mode, you can
|
||||||
* directly export the Store instantiation;
|
* directly export the Store instantiation;
|
||||||
@@ -22,7 +9,7 @@ declare module 'pinia' {
|
|||||||
* with the Store instance.
|
* with the Store instance.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default store((/* { ssrContext } */) => {
|
export default defineStore((/* { ssrContext } */) => {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|
||||||
// You can add Pinia plugins here
|
// You can add Pinia plugins here
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const useIntervalStore = defineStore('interval', () => {
|
|||||||
realtimeStore.register(
|
realtimeStore.register(
|
||||||
`databases.${AppwriteIds.databaseId}.collections.${AppwriteIds.collection.interval}.documents`,
|
`databases.${AppwriteIds.databaseId}.collections.${AppwriteIds.collection.interval}.documents`,
|
||||||
(response) => {
|
(response) => {
|
||||||
const payload = response.payload as Interval;
|
const payload = response.payload as unknown as Interval;
|
||||||
if (!payload.$id) return;
|
if (!payload.$id) return;
|
||||||
if (
|
if (
|
||||||
response.events.includes('databases.*.collections.*.documents.*.delete')
|
response.events.includes('databases.*.collections.*.documents.*.delete')
|
||||||
@@ -92,7 +92,7 @@ export const useIntervalStore = defineStore('interval', () => {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
response.documents.forEach((d) =>
|
response.documents.forEach((d) =>
|
||||||
intervals.value.set(d.$id, d as Interval)
|
intervals.value.set(d.$id, d as unknown as Interval)
|
||||||
);
|
);
|
||||||
dateStatus.value.set(dateString, 'loaded');
|
dateStatus.value.set(dateString, 'loaded');
|
||||||
console.info(`Loaded ${response.documents.length} intervals from server`);
|
console.info(`Loaded ${response.documents.length} intervals from server`);
|
||||||
@@ -110,7 +110,7 @@ export const useIntervalStore = defineStore('interval', () => {
|
|||||||
ID.unique(),
|
ID.unique(),
|
||||||
interval
|
interval
|
||||||
);
|
);
|
||||||
intervals.value.set(response.$id, response as Interval);
|
intervals.value.set(response.$id, response as unknown as Interval);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error creating Interval: ' + e);
|
console.error('Error creating Interval: ' + e);
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ export const useIntervalStore = defineStore('interval', () => {
|
|||||||
interval.$id,
|
interval.$id,
|
||||||
{ ...interval, $id: undefined }
|
{ ...interval, $id: undefined }
|
||||||
);
|
);
|
||||||
intervals.value.set(response.$id, response as Interval);
|
intervals.value.set(response.$id, response as unknown as Interval);
|
||||||
console.info(`Saved Interval: ${interval.$id}`);
|
console.info(`Saved Interval: ${interval.$id}`);
|
||||||
} else {
|
} else {
|
||||||
console.error('Update interval called without an ID');
|
console.error('Update interval called without an ID');
|
||||||
|
|||||||
@@ -20,14 +20,13 @@ export const useIntervalTemplateStore = defineStore('intervalTemplate', () => {
|
|||||||
AppwriteIds.databaseId,
|
AppwriteIds.databaseId,
|
||||||
AppwriteIds.collection.intervalTemplate
|
AppwriteIds.collection.intervalTemplate
|
||||||
);
|
);
|
||||||
intervalTemplates.value = response.documents.map(
|
intervalTemplates.value = response.documents.map((d): IntervalTemplate => {
|
||||||
(d: Models.Document): IntervalTemplate => {
|
const doc = d as unknown as { timeTuple: string[] } & Models.Document;
|
||||||
return {
|
return {
|
||||||
...d,
|
...doc,
|
||||||
timeTuples: arrayToTimeTuples(d.timeTuple),
|
timeTuples: arrayToTimeTuples(doc.timeTuple),
|
||||||
} as IntervalTemplate;
|
} as unknown as IntervalTemplate;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch timeblock templates', error);
|
console.error('Failed to fetch timeblock templates', error);
|
||||||
}
|
}
|
||||||
@@ -41,7 +40,7 @@ export const useIntervalTemplateStore = defineStore('intervalTemplate', () => {
|
|||||||
ID.unique(),
|
ID.unique(),
|
||||||
{ name: template.name, timeTuple: template.timeTuples.flat(2) }
|
{ name: template.name, timeTuple: template.timeTuples.flat(2) }
|
||||||
);
|
);
|
||||||
intervalTemplates.value.push(response as IntervalTemplate);
|
intervalTemplates.value.push(response as unknown as IntervalTemplate);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error updating IntervalTemplate: ' + e);
|
console.error('Error updating IntervalTemplate: ' + e);
|
||||||
}
|
}
|
||||||
@@ -79,8 +78,10 @@ export const useIntervalTemplateStore = defineStore('intervalTemplate', () => {
|
|||||||
? b
|
? b
|
||||||
: ({
|
: ({
|
||||||
...response,
|
...response,
|
||||||
timeTuples: arrayToTimeTuples(response.timeTuple),
|
timeTuples: arrayToTimeTuples(
|
||||||
} as IntervalTemplate)
|
(response as unknown as { timeTuple: string[] }).timeTuple
|
||||||
|
),
|
||||||
|
} as unknown as IntervalTemplate)
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error updating IntervalTemplate: ' + e);
|
console.error('Error updating IntervalTemplate: ' + e);
|
||||||
|
|||||||
@@ -20,33 +20,33 @@ function getSampleData(): ReferenceEntry[] {
|
|||||||
content: `Its hard to imagine that a modern 27 foot sailboat with a classic look, superb
|
content: `Its hard to imagine that a modern 27 foot sailboat with a classic look, superb
|
||||||
stability, and easy to manage rig is such a fast boat. 123-126 PHRF. No longer do you
|
stability, and easy to manage rig is such a fast boat. 123-126 PHRF. No longer do you
|
||||||
have to substitute speed for comfort, or own separate boats for racing and cruising. The
|
have to substitute speed for comfort, or own separate boats for racing and cruising. The
|
||||||
8\’ long cockpit seats 4 to 5 comfortably. Below deck you can sleep 5. And with head and
|
8’ long cockpit seats 4 to 5 comfortably. Below deck you can sleep 5. And with head and
|
||||||
stove, the J/27 is the perfect weekend cruiser.
|
stove, the J/27 is the perfect weekend cruiser.
|
||||||
|
|
||||||
Fun and Fast. There are some impressive victories to back this up, but that doesn\’t
|
Fun and Fast. There are some impressive victories to back this up, but that doesn’t
|
||||||
tell the whole story. The J/27 is fun and responsive. Nothing is more exhilarating than
|
tell the whole story. The J/27 is fun and responsive. Nothing is more exhilarating than
|
||||||
popping the J/27\’s kite in a good breeze for a downhill sleigh ride. 15+ knots planing
|
popping the J/27’s kite in a good breeze for a downhill sleigh ride. 15+ knots planing
|
||||||
off the wave-tops is easy. And most importantly, this off-wind speed doesn\’t sacrifice
|
off the wave-tops is easy. And most importantly, this off-wind speed doesn’t sacrifice
|
||||||
upwind performance. Going to windward in the J/27 is a dream, it has the solid, balanced
|
upwind performance. Going to windward in the J/27 is a dream, it has the solid, balanced
|
||||||
"feel" of a traditional keelboat. The J/27 points higher and goes faster than many 30-35
|
"feel" of a traditional keelboat. The J/27 points higher and goes faster than many 30-35
|
||||||
footers!
|
footers!
|
||||||
|
|
||||||
One-Design Racing. Even more fun is sailing a one-design race around the buoys. The
|
One-Design Racing. Even more fun is sailing a one-design race around the buoys. The
|
||||||
J/27\’s close-windedness makes it very tactical, as even 5 degree wind shifts bring
|
J/27’s close-windedness makes it very tactical, as even 5 degree wind shifts bring
|
||||||
significant gains. Then off wind, you quickly learn to play gibe angles as the boat\’s
|
significant gains. Then off wind, you quickly learn to play gibe angles as the boat’s
|
||||||
acceleration gains you valuable ground on the competition. The J/27 is remarkably agile
|
acceleration gains you valuable ground on the competition. The J/27 is remarkably agile
|
||||||
and responsive in lighter winds, which is unusual for a boat that feels so solid.
|
and responsive in lighter winds, which is unusual for a boat that feels so solid.
|
||||||
|
|
||||||
All-Day Comfort. Sailing past larger boats is always satisfying... especially when it\’s
|
All-Day Comfort. Sailing past larger boats is always satisfying... especially when it’s
|
||||||
effortless and you can\’t be written off as being wet and uncomfortable. Design is the
|
effortless and you can’t be written off as being wet and uncomfortable. Design is the
|
||||||
difference. It\’s all done from a cockpit which holds several people more than is possible
|
difference. It’s all done from a cockpit which holds several people more than is possible
|
||||||
on other 27-footers. Correctly angled backrests and decks at elbow level provide restful
|
on other 27-footers. Correctly angled backrests and decks at elbow level provide restful
|
||||||
and secure seating. Harken mainsheet, vang, traveler, and backstay systems; four Barient
|
and secure seating. Harken mainsheet, vang, traveler, and backstay systems; four Barient
|
||||||
winches; a beautiful double spreader, tapered, fractional rig spar by Hall . . . make
|
winches; a beautiful double spreader, tapered, fractional rig spar by Hall . . . make
|
||||||
control and adjustment easy for crew members no matter what the wind.
|
control and adjustment easy for crew members no matter what the wind.
|
||||||
|
|
||||||
Get-away Weekend Cruiser. Take a break from the pace of life on land and spend time with
|
Get-away Weekend Cruiser. Take a break from the pace of life on land and spend time with
|
||||||
family and friends sailing the J/27. It's a fun boat to sail, so everyone becomes involved.
|
family and friends sailing the J/27. It’s a fun boat to sail, so everyone becomes involved.
|
||||||
|
|
||||||
The visibility, when steering with a responsive tiller gives the inexperienced that sense
|
The visibility, when steering with a responsive tiller gives the inexperienced that sense
|
||||||
of control not found when spinning a tiny wheel on small cruisers with large trunk cabins.
|
of control not found when spinning a tiny wheel on small cruisers with large trunk cabins.
|
||||||
@@ -57,14 +57,14 @@ function getSampleData(): ReferenceEntry[] {
|
|||||||
starboard is a comfortable quarter berth. Enough room below for a family of four or a
|
starboard is a comfortable quarter berth. Enough room below for a family of four or a
|
||||||
couple for a nice weekend romp to your favorite sailing anchorage.
|
couple for a nice weekend romp to your favorite sailing anchorage.
|
||||||
|
|
||||||
Durable and Stable. The J/27\’s secure big boat feel is created by concentrating 1530
|
Durable and Stable. The J/27’s secure big boat feel is created by concentrating 1530
|
||||||
pounds of lead very low in the keel while using high strength to eight ratio laminates
|
pounds of lead very low in the keel while using high strength to eight ratio laminates
|
||||||
in the hull. Unidirectional E-glass on either side of pre-sealed Baltek CK57 aircraft
|
in the hull. Unidirectional E-glass on either side of pre-sealed Baltek CK57 aircraft
|
||||||
grade, Lloyd\’s approved, end grain balsa sandwich construction means superior torsion and
|
grade, Lloyd’s approved, end grain balsa sandwich construction means superior torsion and
|
||||||
impact resistance. Light ends, low freeboard, and the low center of gravity of a lead keel
|
impact resistance. Light ends, low freeboard, and the low center of gravity of a lead keel
|
||||||
coupled with low wetted surface and a generous sailplan of 362 sq. ft. achieves exceptional
|
coupled with low wetted surface and a generous sailplan of 362 sq. ft. achieves exceptional
|
||||||
sail area and stability relative to displacement. Hence, sparkling performance in both
|
sail area and stability relative to displacement. Hence, sparkling performance in both
|
||||||
light and heavy air...something that doesn\’t happen with iron keels and box-like hulls.
|
light and heavy air...something that doesn’t happen with iron keels and box-like hulls.
|
||||||
|
|
||||||
Strong Class Strict Rules. The J/27 Class Association, owner driven and over 190 boats
|
Strong Class Strict Rules. The J/27 Class Association, owner driven and over 190 boats
|
||||||
strong, sail in North American, Midwinter, and Regional championships. A superb J/27 Class
|
strong, sail in North American, Midwinter, and Regional championships. A superb J/27 Class
|
||||||
@@ -113,7 +113,7 @@ export const useReferenceStore = defineStore('reference', {
|
|||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
getCategory(state) {
|
getCategory(state) {
|
||||||
(category: string) => {
|
return (category: string) => {
|
||||||
return state.allItems.filter((c) => c.category === category);
|
return state.allItems.filter((c) => c.category === category);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
|||||||
realtimeStore.register(
|
realtimeStore.register(
|
||||||
`databases.${AppwriteIds.databaseId}.collections.${AppwriteIds.collection.reservation}.documents`,
|
`databases.${AppwriteIds.databaseId}.collections.${AppwriteIds.collection.reservation}.documents`,
|
||||||
(response) => {
|
(response) => {
|
||||||
const payload = response.payload as Reservation;
|
const payload = response.payload as unknown as Reservation;
|
||||||
if (payload.$id) {
|
if (payload.$id) {
|
||||||
if (
|
if (
|
||||||
response.events.includes(
|
response.events.includes(
|
||||||
@@ -62,7 +62,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
response.documents.forEach((d) =>
|
response.documents.forEach((d) =>
|
||||||
reservations.set(d.$id, d as Reservation)
|
reservations.set(d.$id, d as unknown as Reservation)
|
||||||
);
|
);
|
||||||
setDateLoaded(startDate, endDate, 'loaded');
|
setDateLoaded(startDate, endDate, 'loaded');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -77,7 +77,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
|||||||
AppwriteIds.collection.reservation,
|
AppwriteIds.collection.reservation,
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
return response as Reservation;
|
return response as unknown as Reservation;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch reservation: ', error);
|
console.error('Failed to fetch reservation: ', error);
|
||||||
}
|
}
|
||||||
@@ -103,10 +103,10 @@ export const useReservationStore = defineStore('reservation', () => {
|
|||||||
reservation
|
reservation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
reservations.set(response.$id, response as Reservation);
|
reservations.set(response.$id, response as unknown as Reservation);
|
||||||
userReservations.set(response.$id, response as Reservation);
|
userReservations.set(response.$id, response as unknown as Reservation);
|
||||||
console.info('Reservation booked: ', response);
|
console.info('Reservation booked: ', response);
|
||||||
return response as Reservation;
|
return response as unknown as Reservation;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error creating Reservation: ' + e);
|
console.error('Error creating Reservation: ' + e);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -244,7 +244,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
|||||||
[Query.equal('user', authStore.currentUser.$id)]
|
[Query.equal('user', authStore.currentUser.$id)]
|
||||||
);
|
);
|
||||||
response.documents.forEach((d) =>
|
response.documents.forEach((d) =>
|
||||||
userReservations.set(d.$id, d as Reservation)
|
userReservations.set(d.$id, d as unknown as Reservation)
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch reservations for user: ', error);
|
console.error('Failed to fetch reservations for user: ', error);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type {
|
|||||||
} from '../schedule.types';
|
} from '../schedule.types';
|
||||||
|
|
||||||
export const templateA: IntervalTemplate = {
|
export const templateA: IntervalTemplate = {
|
||||||
id: '1',
|
$id: '1',
|
||||||
name: 'WeekdayBlocks',
|
name: 'WeekdayBlocks',
|
||||||
timeTuples: [
|
timeTuples: [
|
||||||
['08:00', '12:00'],
|
['08:00', '12:00'],
|
||||||
@@ -26,7 +26,7 @@ export const templateA: IntervalTemplate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const templateB: IntervalTemplate = {
|
export const templateB: IntervalTemplate = {
|
||||||
id: '2',
|
$id: '2',
|
||||||
name: 'WeekendBlocks',
|
name: 'WeekendBlocks',
|
||||||
timeTuples: [
|
timeTuples: [
|
||||||
['07:00', '10:00'],
|
['07:00', '10:00'],
|
||||||
@@ -47,7 +47,7 @@ export function getSampleIntervals(): Interval[] {
|
|||||||
result.push(
|
result.push(
|
||||||
...boats
|
...boats
|
||||||
.map((b): Interval[] => {
|
.map((b): Interval[] => {
|
||||||
return template.blocks.map((t: TimeTuple): Interval => {
|
return template.timeTuples.map((t: TimeTuple): Interval => {
|
||||||
return {
|
return {
|
||||||
$id: 'id' + Math.random().toString(32).slice(2),
|
$id: 'id' + Math.random().toString(32).slice(2),
|
||||||
resource: b.$id,
|
resource: b.$id,
|
||||||
@@ -135,14 +135,13 @@ export function getSampleReservations(): Reservation[] {
|
|||||||
return sampleData.map((entry): Reservation => {
|
return sampleData.map((entry): Reservation => {
|
||||||
const boat = <Boat>boatStore.boats.find((b) => b.$id == entry.boat);
|
const boat = <Boat>boatStore.boats.find((b) => b.$id == entry.boat);
|
||||||
return {
|
return {
|
||||||
id: entry.id,
|
$id: entry.id,
|
||||||
user: entry.user,
|
user: entry.user,
|
||||||
start: date
|
start: date
|
||||||
.adjustDate(now, makeOpts(splitTime(entry.start)))
|
.adjustDate(now, makeOpts(splitTime(entry.start)))
|
||||||
.toISOString(),
|
.toISOString(),
|
||||||
end: date.adjustDate(now, makeOpts(splitTime(entry.end))).toISOString(),
|
end: date.adjustDate(now, makeOpts(splitTime(entry.end))).toISOString(),
|
||||||
resource: boat.$id,
|
resource: boat.$id,
|
||||||
reservationDate: now,
|
|
||||||
reason: entry.reason,
|
reason: entry.reason,
|
||||||
status: entry.status as StatusTypes,
|
status: entry.status as StatusTypes,
|
||||||
comment: '',
|
comment: '',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export type Interval = Partial<Models.Document> & {
|
|||||||
resource: string;
|
resource: string;
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
|
user?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IntervalTemplate = Partial<Models.Document> & {
|
export type IntervalTemplate = Partial<Models.Document> & {
|
||||||
|
|||||||
10
src/stores/store-flag.d.ts
vendored
10
src/stores/store-flag.d.ts
vendored
@@ -1,10 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
|
||||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
|
||||||
import "quasar/dist/types/feature-flag";
|
|
||||||
|
|
||||||
declare module "quasar/dist/types/feature-flag" {
|
|
||||||
interface QuasarFeatureFlags {
|
|
||||||
store: true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,7 +50,7 @@ export const useTaskStore = defineStore('tasks', {
|
|||||||
AppwriteIds.databaseId,
|
AppwriteIds.databaseId,
|
||||||
AppwriteIds.collection.task
|
AppwriteIds.collection.task
|
||||||
);
|
);
|
||||||
this.tasks = response.documents as Task[];
|
this.tasks = response.documents as unknown as Task[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch tasks', error);
|
console.error('Failed to fetch tasks', error);
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ export const useTaskStore = defineStore('tasks', {
|
|||||||
AppwriteIds.databaseId,
|
AppwriteIds.databaseId,
|
||||||
AppwriteIds.collection.taskTags
|
AppwriteIds.collection.taskTags
|
||||||
);
|
);
|
||||||
this.taskTags = response.documents as TaskTag[];
|
this.taskTags = response.documents as unknown as TaskTag[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch task tags', error);
|
console.error('Failed to fetch task tags', error);
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ export const useTaskStore = defineStore('tasks', {
|
|||||||
AppwriteIds.databaseId,
|
AppwriteIds.databaseId,
|
||||||
AppwriteIds.collection.skillTags
|
AppwriteIds.collection.skillTags
|
||||||
);
|
);
|
||||||
this.skillTags = response.documents as SkillTag[];
|
this.skillTags = response.documents as unknown as SkillTag[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch skill tags', error);
|
console.error('Failed to fetch skill tags', error);
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ export const useTaskStore = defineStore('tasks', {
|
|||||||
ID.unique(),
|
ID.unique(),
|
||||||
newTask
|
newTask
|
||||||
);
|
);
|
||||||
this.tasks.push(response as Task);
|
this.tasks.push(response as unknown as Task);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to add task:', error);
|
console.error('Failed to add task:', error);
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ export const useTaskStore = defineStore('tasks', {
|
|||||||
task.$id,
|
task.$id,
|
||||||
newTask
|
newTask
|
||||||
);
|
);
|
||||||
this.tasks.push(response as Task);
|
this.tasks.push(response as unknown as Task);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update task:', error);
|
console.error('Failed to update task:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export const APP_VERSION = '0.0.0';
|
|
||||||
490
templates/claude-templates.md
Normal file
490
templates/claude-templates.md
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
# Claude Templates — On-Demand Reference
|
||||||
|
|
||||||
|
> **Do NOT read this file at session start.** Read it only when you need to write a summary, handoff, decision record, or subagent output. This file is referenced from CLAUDE.md.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template 1: Source Document Summary
|
||||||
|
|
||||||
|
**Use when:** Processing any input document (client brief, research report, requirements doc, existing proposal)
|
||||||
|
|
||||||
|
**Write to:** `./docs/summaries/source-[filename].md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Source Summary: [Original Document Name]
|
||||||
|
**Processed:** [YYYY-MM-DD]
|
||||||
|
**Source Path:** [exact file path]
|
||||||
|
**Archived From:** [original path, if moved to docs/archive/]
|
||||||
|
**Document Type:** [brief / requirements / research / proposal / transcript / other]
|
||||||
|
**Confidence:** [high = I understood everything / medium = some interpretation needed / low = significant gaps]
|
||||||
|
|
||||||
|
## Exact Numbers & Metrics
|
||||||
|
<!-- List EVERY specific number, dollar amount, percentage, date, count, measurement.
|
||||||
|
Do NOT round. Do NOT paraphrase. Copy exactly as stated in source. -->
|
||||||
|
- [metric]: [exact value] (page/section reference if available)
|
||||||
|
- [metric]: [exact value]
|
||||||
|
|
||||||
|
## Key Facts (Confirmed)
|
||||||
|
<!-- Only include facts explicitly stated in the document. Tag source. -->
|
||||||
|
- [fact] — stated in [section/page]
|
||||||
|
- [fact] — stated in [section/page]
|
||||||
|
|
||||||
|
## Requirements & Constraints
|
||||||
|
<!-- Use IF/THEN/BUT/EXCEPT format to preserve conditional logic -->
|
||||||
|
- REQUIREMENT: [what is needed]
|
||||||
|
- CONDITION: [when/if this applies]
|
||||||
|
- CONSTRAINT: [limitation or exception]
|
||||||
|
- PRIORITY: [must-have / should-have / nice-to-have / stated by whom]
|
||||||
|
|
||||||
|
## Decisions Referenced
|
||||||
|
<!-- Any decisions mentioned in the document -->
|
||||||
|
- DECISION: [what was decided]
|
||||||
|
- RATIONALE: [why, as stated in document]
|
||||||
|
- ALTERNATIVES MENTIONED: [what else was considered]
|
||||||
|
- DECIDED BY: [who, if stated]
|
||||||
|
|
||||||
|
## Relationships to Other Documents
|
||||||
|
<!-- How this document connects to other known project documents -->
|
||||||
|
- SUPPORTS: [other document/decision it reinforces]
|
||||||
|
- CONTRADICTS: [other document/decision it conflicts with]
|
||||||
|
- DEPENDS ON: [other document/decision it requires]
|
||||||
|
- UPDATES: [other document/decision it supersedes]
|
||||||
|
|
||||||
|
## Open Questions & Ambiguities
|
||||||
|
<!-- Things that are NOT resolved in this document -->
|
||||||
|
- UNCLEAR: [what is ambiguous] — needs clarification from [whom]
|
||||||
|
- ASSUMED: [interpretation made] — verify with [whom]
|
||||||
|
- MISSING: [information referenced but not provided]
|
||||||
|
|
||||||
|
## Verbatim Quotes Worth Preserving
|
||||||
|
<!-- 2-5 direct quotes that capture stakeholder language, priorities, or constraints
|
||||||
|
These are critical for proposals — use the client's own words back to them -->
|
||||||
|
- "[exact quote]" — [speaker/author], [context]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template 2: Analysis / Research Summary
|
||||||
|
|
||||||
|
**Use when:** Completing competitive analysis, market research, technical evaluation
|
||||||
|
|
||||||
|
**Write to:** `./docs/summaries/analysis-[topic].md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Analysis Summary: [Topic]
|
||||||
|
**Completed:** [YYYY-MM-DD]
|
||||||
|
**Analysis Type:** [competitive / market / technical / financial / feasibility]
|
||||||
|
**Sources Used:** [list source paths or URLs]
|
||||||
|
**Confidence:** [high / medium / low — and WHY this confidence level]
|
||||||
|
|
||||||
|
## Core Finding (One Sentence)
|
||||||
|
[Single sentence: the most important conclusion]
|
||||||
|
|
||||||
|
## Evidence Base
|
||||||
|
<!-- Specific data points supporting the finding. Exact numbers only. -->
|
||||||
|
| Data Point | Value | Source | Date of Data |
|
||||||
|
|-----------|-------|--------|-------------|
|
||||||
|
| [metric] | [exact value] | [source] | [date] |
|
||||||
|
|
||||||
|
## Detailed Findings
|
||||||
|
### Finding 1: [Name]
|
||||||
|
- WHAT: [the finding]
|
||||||
|
- SO WHAT: [why it matters for this project]
|
||||||
|
- EVIDENCE: [specific supporting data]
|
||||||
|
- CONFIDENCE: [high/medium/low]
|
||||||
|
|
||||||
|
### Finding 2: [Name]
|
||||||
|
[same structure]
|
||||||
|
|
||||||
|
## Conditional Conclusions
|
||||||
|
<!-- Use IF/THEN format -->
|
||||||
|
- IF [condition], THEN [conclusion], BECAUSE [evidence]
|
||||||
|
- IF [alternative condition], THEN [different conclusion]
|
||||||
|
|
||||||
|
## What This Analysis Does NOT Cover
|
||||||
|
<!-- Explicit scope boundaries to prevent future sessions from over-interpreting -->
|
||||||
|
- [topic not addressed and why]
|
||||||
|
- [data not available]
|
||||||
|
|
||||||
|
## Recommended Next Steps
|
||||||
|
1. [action] — priority [high/medium/low], depends on [what]
|
||||||
|
2. [action]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template 3: Decision Record
|
||||||
|
|
||||||
|
**Use when:** Any significant decision is made during a session
|
||||||
|
|
||||||
|
**Write to:** `./docs/summaries/decision-[number]-[topic].md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Decision Record: [Short Title]
|
||||||
|
**Decision ID:** DR-[sequential number]
|
||||||
|
**Date:** [YYYY-MM-DD]
|
||||||
|
**Status:** CONFIRMED / PROVISIONAL / REQUIRES_VALIDATION
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
[One clear sentence: what was decided]
|
||||||
|
|
||||||
|
## Context
|
||||||
|
[2-3 sentences: what situation prompted this decision]
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
- CHOSE [option] BECAUSE: [specific reasons with data]
|
||||||
|
- REJECTED [alternative 1] BECAUSE: [specific reasons]
|
||||||
|
- REJECTED [alternative 2] BECAUSE: [specific reasons]
|
||||||
|
|
||||||
|
## Quantified Impact
|
||||||
|
- [metric affected]: [expected change with numbers]
|
||||||
|
- [cost/time/resource implication]: [specific figures]
|
||||||
|
|
||||||
|
## Conditions & Constraints
|
||||||
|
- VALID IF: [conditions under which this decision holds]
|
||||||
|
- REVISIT IF: [triggers that should cause reconsideration]
|
||||||
|
- DEPENDS ON: [upstream decisions or facts this relies on]
|
||||||
|
|
||||||
|
## Stakeholder Input
|
||||||
|
- [name/role]: [their stated position, if known]
|
||||||
|
|
||||||
|
## Downstream Effects
|
||||||
|
- AFFECTS: [what other decisions, documents, or deliverables this impacts]
|
||||||
|
- REQUIRES UPDATE TO: [specific files or deliverables that need revision]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template 4: Session Handoff
|
||||||
|
|
||||||
|
**Use when:** A session is ending (context limit approaching OR phase complete)
|
||||||
|
|
||||||
|
**Write to:** `./docs/summaries/handoff-[YYYY-MM-DD]-[topic].md`
|
||||||
|
|
||||||
|
**LIFECYCLE**: After writing a new handoff, move the PREVIOUS handoff to `docs/archive/handoffs/`.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Session Handoff: [Topic]
|
||||||
|
**Date:** [YYYY-MM-DD]
|
||||||
|
**Session Duration:** [approximate]
|
||||||
|
**Session Focus:** [one sentence]
|
||||||
|
**Context Usage at Handoff:** [estimated percentage if known]
|
||||||
|
|
||||||
|
## What Was Accomplished
|
||||||
|
<!-- Be specific. Include file paths for every output. -->
|
||||||
|
1. [task completed] → output at `[file path]`
|
||||||
|
2. [task completed] → output at `[file path]`
|
||||||
|
|
||||||
|
## Exact State of Work in Progress
|
||||||
|
<!-- If anything is mid-stream, describe exactly where it stopped -->
|
||||||
|
- [work item]: completed through [specific point], next step is [specific action]
|
||||||
|
- [work item]: blocked on [specific issue]
|
||||||
|
|
||||||
|
## Decisions Made This Session
|
||||||
|
<!-- Reference decision records if created, otherwise summarize here -->
|
||||||
|
- DR-[number]: [decision] (see `./docs/summaries/decision-[file]`)
|
||||||
|
- [Ad-hoc decision]: [what] BECAUSE [why] — STATUS: [confirmed/provisional]
|
||||||
|
|
||||||
|
## Key Numbers Generated or Discovered This Session
|
||||||
|
<!-- Every metric, estimate, or figure produced. Exact values. -->
|
||||||
|
- [metric]: [value] — [context for where/how this was derived]
|
||||||
|
|
||||||
|
## Conditional Logic Established
|
||||||
|
<!-- Any IF/THEN/BUT/EXCEPT reasoning that future sessions must respect -->
|
||||||
|
- IF [condition] THEN [approach] BECAUSE [rationale]
|
||||||
|
|
||||||
|
## Files Created or Modified
|
||||||
|
| File Path | Action | Description |
|
||||||
|
|-----------|--------|-------------|
|
||||||
|
| `[path]` | Created | [what it contains] |
|
||||||
|
| `[path]` | Modified | [what changed and why] |
|
||||||
|
|
||||||
|
## What the NEXT Session Should Do
|
||||||
|
<!-- Ordered, specific instructions. The next session starts by reading this. -->
|
||||||
|
1. **First**: [specific action with file paths]
|
||||||
|
2. **Then**: [specific action]
|
||||||
|
3. **Then**: [specific action]
|
||||||
|
|
||||||
|
## Open Questions Requiring User Input
|
||||||
|
<!-- Do NOT proceed on these without explicit user confirmation -->
|
||||||
|
- [ ] [question] — impacts [what downstream deliverable]
|
||||||
|
- [ ] [question]
|
||||||
|
|
||||||
|
## Assumptions That Need Validation
|
||||||
|
<!-- Things treated as true this session but not confirmed -->
|
||||||
|
- ASSUMED: [assumption] — validate by [method/person]
|
||||||
|
|
||||||
|
## What NOT to Re-Read
|
||||||
|
<!-- Prevent the next session from wasting context on already-processed material -->
|
||||||
|
- `[file path]` — already summarized in `[summary file path]`
|
||||||
|
|
||||||
|
## Files to Load Next Session
|
||||||
|
<!-- Explicit index of what the next session should read. Acts as progressive disclosure index layer. -->
|
||||||
|
- `[file path]` — needed for [reason]
|
||||||
|
- `[file path]` — needed for [reason]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template 5: Project Brief (Initial Setup)
|
||||||
|
|
||||||
|
**Use when:** Creating the 00-project-brief.md at project start
|
||||||
|
|
||||||
|
**Write to:** `./docs/summaries/00-project-brief.md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Project Brief: [Project Name]
|
||||||
|
**Created:** [YYYY-MM-DD]
|
||||||
|
**Last Updated:** [YYYY-MM-DD]
|
||||||
|
|
||||||
|
## Client
|
||||||
|
- **Name:** [client name]
|
||||||
|
- **Industry:** [industry]
|
||||||
|
- **Size:** [employee count / revenue if known]
|
||||||
|
- **Relationship:** [through AutomatonsX / Lagrange Data / direct / other]
|
||||||
|
- **Key Contacts:** [names and roles if known]
|
||||||
|
|
||||||
|
## Engagement
|
||||||
|
- **Type:** [proposal / workshop / competitive analysis / agent development / hybrid]
|
||||||
|
- **Scope:** [one paragraph description]
|
||||||
|
- **Target Deliverable:** [specific output expected]
|
||||||
|
- **Timeline:** [deadline if known]
|
||||||
|
- **Budget Context:** [if known — exact figures]
|
||||||
|
|
||||||
|
## Input Documents
|
||||||
|
| Document | Path | Processed? | Summary At |
|
||||||
|
|----------|------|-----------|------------|
|
||||||
|
| [name] | `[path]` | Yes/No | `[summary path]` |
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
- [criterion 1]
|
||||||
|
- [criterion 2]
|
||||||
|
|
||||||
|
## Known Constraints
|
||||||
|
- [constraint 1]
|
||||||
|
- [constraint 2]
|
||||||
|
|
||||||
|
## Project Phase Tracker
|
||||||
|
| Phase | Status | Summary File | Date |
|
||||||
|
|-------|--------|-------------|------|
|
||||||
|
| Discovery | Not Started / In Progress / Complete | `[path]` | |
|
||||||
|
| Strategy | Not Started / In Progress / Complete | `[path]` | |
|
||||||
|
| Deliverable Draft | Not Started / In Progress / Complete | `[path]` | |
|
||||||
|
| Review & Polish | Not Started / In Progress / Complete | `[path]` | |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template 6: Task Definition
|
||||||
|
|
||||||
|
**Use when:** Defining a discrete unit of work before starting execution
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Task: [name]
|
||||||
|
**Date:** [YYYY-MM-DD]
|
||||||
|
**Client:** [if applicable]
|
||||||
|
**Work Type:** [proposal / workshop / analysis / content / agent development]
|
||||||
|
|
||||||
|
### Context Files to Load
|
||||||
|
- `[file path]` — [why needed]
|
||||||
|
|
||||||
|
### Action
|
||||||
|
[What to produce. Be specific about format, length, and scope.]
|
||||||
|
|
||||||
|
### Verify
|
||||||
|
- [ ] Numbers match source data exactly
|
||||||
|
- [ ] Open questions marked OPEN
|
||||||
|
- [ ] Output matches what was requested, not what was assumed
|
||||||
|
- [ ] Claims backed by specific data
|
||||||
|
- [ ] Consistent with stored decisions in docs/context/
|
||||||
|
|
||||||
|
### Done When
|
||||||
|
- [ ] Output file exists at `[specific path]`
|
||||||
|
- [ ] Summary written to `docs/summaries/[specific file]`
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Subagent Output Contracts
|
||||||
|
|
||||||
|
**CRITICAL: When subagents return results to the main agent, unstructured prose causes information loss. These output contracts define the EXACT format subagents must return.**
|
||||||
|
|
||||||
|
### Contract for Document Analysis Subagent
|
||||||
|
|
||||||
|
```
|
||||||
|
=== DOCUMENT ANALYSIS OUTPUT ===
|
||||||
|
SOURCE: [file path]
|
||||||
|
TYPE: [document type]
|
||||||
|
CONFIDENCE: [high/medium/low]
|
||||||
|
|
||||||
|
NUMBERS:
|
||||||
|
- [metric]: [exact value]
|
||||||
|
[repeat for all numbers found]
|
||||||
|
|
||||||
|
REQUIREMENTS:
|
||||||
|
- REQ: [requirement] | CONDITION: [if any] | PRIORITY: [level] | CONSTRAINT: [if any]
|
||||||
|
[repeat]
|
||||||
|
|
||||||
|
DECISIONS_REFERENCED:
|
||||||
|
- DEC: [what] | WHY: [rationale] | BY: [who]
|
||||||
|
[repeat]
|
||||||
|
|
||||||
|
CONTRADICTIONS:
|
||||||
|
- [this document says X] CONTRADICTS [other known fact Y]
|
||||||
|
[repeat or NONE]
|
||||||
|
|
||||||
|
OPEN:
|
||||||
|
- [unresolved item] | NEEDS: [who/what to resolve]
|
||||||
|
[repeat or NONE]
|
||||||
|
|
||||||
|
QUOTES:
|
||||||
|
- "[verbatim]" — [speaker], [context]
|
||||||
|
[repeat, max 5]
|
||||||
|
|
||||||
|
=== END OUTPUT ===
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contract for Research/Analysis Subagent
|
||||||
|
|
||||||
|
```
|
||||||
|
=== RESEARCH OUTPUT ===
|
||||||
|
QUERY: [what was researched]
|
||||||
|
SOURCES: [list]
|
||||||
|
CONFIDENCE: [high/medium/low] BECAUSE [reason]
|
||||||
|
|
||||||
|
CORE_FINDING: [one sentence]
|
||||||
|
|
||||||
|
EVIDENCE:
|
||||||
|
- [data point]: [exact value] | SOURCE: [where] | DATE: [when]
|
||||||
|
[repeat]
|
||||||
|
|
||||||
|
CONCLUSIONS:
|
||||||
|
- IF [condition] THEN [conclusion] | EVIDENCE: [reference]
|
||||||
|
[repeat]
|
||||||
|
|
||||||
|
GAPS:
|
||||||
|
- [what was not found or not covered]
|
||||||
|
[repeat or NONE]
|
||||||
|
|
||||||
|
NEXT_STEPS:
|
||||||
|
- [recommended action] | PRIORITY: [level]
|
||||||
|
[repeat]
|
||||||
|
|
||||||
|
=== END OUTPUT ===
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contract for Review/QA Subagent
|
||||||
|
|
||||||
|
```
|
||||||
|
=== REVIEW OUTPUT ===
|
||||||
|
REVIEWED: [file path or deliverable name]
|
||||||
|
AGAINST: [what standard — spec, requirements, style guide]
|
||||||
|
|
||||||
|
PASS: [yes/no/partial]
|
||||||
|
|
||||||
|
ISSUES:
|
||||||
|
- SEVERITY: [critical/major/minor] | ITEM: [description] | LOCATION: [where in document] | FIX: [suggested resolution]
|
||||||
|
[repeat]
|
||||||
|
|
||||||
|
MISSING:
|
||||||
|
- [expected content/section not found] | REQUIRED_BY: [which requirement]
|
||||||
|
[repeat or NONE]
|
||||||
|
|
||||||
|
INCONSISTENCIES:
|
||||||
|
- [item A says X] BUT [item B says Y] | RESOLUTION: [suggested]
|
||||||
|
[repeat or NONE]
|
||||||
|
|
||||||
|
STRENGTHS:
|
||||||
|
- [what works well — for positive reinforcement in iteration]
|
||||||
|
[max 3]
|
||||||
|
|
||||||
|
=== END OUTPUT ===
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase-Based Workflow Templates
|
||||||
|
|
||||||
|
### Template A: Enterprise Sales Deliverable
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: Discovery & Input Processing
|
||||||
|
├── Process all client documents → Source Document Summaries
|
||||||
|
├── Identify gaps in information → flag as OPEN items
|
||||||
|
├── Create Decision Records for any choices made
|
||||||
|
├── Write: ./docs/summaries/01-discovery-complete.md (Handoff Template)
|
||||||
|
├── → Suggest new session for Phase 2
|
||||||
|
|
||||||
|
Phase 2: Strategy & Positioning
|
||||||
|
├── Read summaries only (NOT source documents)
|
||||||
|
├── Competitive positioning analysis → Analysis Summary
|
||||||
|
├── Value proposition development
|
||||||
|
├── ROI framework construction with EXACT numbers
|
||||||
|
├── Write: ./docs/summaries/02-strategy-complete.md (Handoff Template)
|
||||||
|
├── → Suggest new session for Phase 3
|
||||||
|
|
||||||
|
Phase 3: Deliverable Creation
|
||||||
|
├── Read strategy summary + project brief only
|
||||||
|
├── Draft deliverable (proposal / deck / workshop plan)
|
||||||
|
├── Output to: ./output/deliverables/
|
||||||
|
├── Write: ./docs/summaries/03-deliverable-draft.md (Handoff Template)
|
||||||
|
├── → Suggest new session for Phase 4
|
||||||
|
|
||||||
|
Phase 4: Review & Polish
|
||||||
|
├── Read draft deliverable + strategy summary
|
||||||
|
├── Quality review using Review/QA Output Contract
|
||||||
|
├── Final edits and formatting
|
||||||
|
├── Output final version to: ./output/deliverables/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Template B: Agent/Application Development
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: Requirements → Spec
|
||||||
|
├── Process all input documents → Source Document Summaries
|
||||||
|
├── Generate structured specification
|
||||||
|
├── Output: ./output/SPEC.md
|
||||||
|
├── Write: ./docs/summaries/01-spec-complete.md (Handoff Template)
|
||||||
|
├── → Suggest new session for Phase 2
|
||||||
|
|
||||||
|
Phase 2: Architecture → Schema
|
||||||
|
├── Read SPEC.md + summaries only
|
||||||
|
├── Design data model
|
||||||
|
├── Define agent behaviors and workflows
|
||||||
|
├── Output: ./output/schemas/data-model.yaml
|
||||||
|
├── Output: ./output/schemas/agent-definitions.yaml
|
||||||
|
├── Write: ./docs/summaries/02-architecture-complete.md (Handoff Template)
|
||||||
|
├── → Suggest new session for Phase 3
|
||||||
|
|
||||||
|
Phase 3: Prompts → Integration
|
||||||
|
├── Read schemas + spec only
|
||||||
|
├── Write system prompts for each agent
|
||||||
|
├── Map API integrations and data flows
|
||||||
|
├── Output: ./output/prompts/[agent-name].md (one per agent)
|
||||||
|
├── Output: ./output/schemas/integration-map.yaml
|
||||||
|
├── Write: ./docs/summaries/03-prompts-complete.md (Handoff Template)
|
||||||
|
├── → Suggest new session for Phase 4
|
||||||
|
|
||||||
|
Phase 4: Assembly → Package
|
||||||
|
├── Read all output files
|
||||||
|
├── Assemble complete application package
|
||||||
|
├── Generate deployment/setup instructions
|
||||||
|
├── Output: ./output/deliverables/[project]-complete-package/
|
||||||
|
├── QA check against original spec using Review/QA Output Contract
|
||||||
|
```
|
||||||
|
|
||||||
|
### Template C: Hybrid (Sales + Agent Development)
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: Client Discovery → Summaries
|
||||||
|
Phase 2: Solution Design → Architecture + Schema
|
||||||
|
Phase 3a: Client-Facing Deliverable (proposal/deck)
|
||||||
|
Phase 3b: Internal Technical Package (schemas/prompts)
|
||||||
|
Phase 4: Review both tracks against each other for consistency
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## End of Templates
|
||||||
|
|
||||||
|
**Return to your task after reading the template(s) you need. Do not keep this file in active context.**
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
{
|
{
|
||||||
"extends": "@quasar/app-vite/tsconfig-preset",
|
"extends": "./.quasar/tsconfig.json"
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user