- TypeScript 82.7%
- Vue 14.1%
- HTML 1.6%
- CSS 1.2%
- JavaScript 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The delivered tokens land as app/assets/css/brand.css, kept as the source of truth so a regenerated charte can overwrite it. Nuxt UI's own tokens are bound to the charte's roles at the end of main.css, and the ramps are aliased in app/app.config.ts — so color="primary" becomes the logo's indigo everywhere without a single component changing. Three deviations from the charte, all for contrast and all measured: - Nuxt UI points each role at its 500 shade in light mode, which fails AA on white (the reason ENF-6 already pinned darker steps). The roles take the charte's 600s: 8.60 / 6.51 / 5.42. - --brand-info is aqua 700, which the charte's OWN table measures at 2.75:1 and marks insuffisant. Info text takes aqua 950; aqua 500 keeps the job the charte gives it, the focus ring. - success/warning/danger are single hexes with no ramp measuring 3.39, 3.19 and 4.83 on white. Status colour is not part of the logo, so those keep the AA-proven shades; the charte's values stay under their --brand-* names. A fourth came out of the dark-mode screenshot: Nuxt UI spends one token on the text of a solid button and of an inverted surface, and the charte's text-inverse only reaches 4.20:1 on the pale indigo a dark solid button becomes. Its own on-primary clears both, at 6.12 and 14.22. --spacing-* is NOT adopted — the one edit inside brand.css, commented in place with its reason. In Tailwind v4 those names are the spacing scale, and the charte's is geometric where Tailwind's is linear: w-10 would go from 2.5rem to 8rem and top-7, which positions the row's delete confirmation, from 1.75rem to 3rem. Type scale, radii, shadows and easings are adopted. The three faces are self-hosted from @fontsource-variable — the charte links fonts.googleapis.com, which CA-12 forbids and font-src 'self' would refuse. Latin + latin-ext only, one variable file per family, added to the PWA precache so the offline shell keeps them. Gates: prettier, eslint, typecheck ×2, 1336 unit (coverage thresholds), 214 e2e including axe on four flows, 3 PWA, build + CSP. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqkRZdkVr7dyx1v3WtDonu |
||
| app | ||
| docs | ||
| i18n/locales | ||
| lib | ||
| public | ||
| scripts | ||
| server | ||
| tests | ||
| tools | ||
| .editorconfig | ||
| .env.example | ||
| .gitignore | ||
| .nuxtrc | ||
| .prettierignore | ||
| .prettierrc | ||
| eslint.config.mjs | ||
| nuxt.config.ts | ||
| package.json | ||
| playwright.config.ts | ||
| playwright.pwa.config.ts | ||
| playwright.smoke.config.ts | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| RALPH.md | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.tests.json | ||
| vitest.config.ts | ||
| vitest.smoke.config.ts | ||
GitSheets
A static single-page app for forge-based timesheet tracking: track work
sessions against issues and merge/pull requests, then sync them as timelogs
on your instance — the forge is the single source of billing truth. Multiple
instances can be registered side by side (Settings → Connections): sessions,
labels/projects settings and the monthly quota are strictly scoped per
instance, and a header switcher flips between them. Remember to include
every instance's origin in GLTS_CONNECT_SRC at build time — and your
storage origin too. A missing origin is not a build error: the browser
silently refuses those requests at runtime and the app reports which origin
its own policy blocked (see docs/SECURITY.md).
Platforms: GitLab and Forgejo. Pick the platform when you add an instance; a fleet may mix the two freely, and each instance keeps its own projects, labels and sessions. Two Forgejo caveats worth knowing before you connect one, because neither is something the app can work around:
- CORS is off by default. A stock Forgejo refuses browser API calls
until an administrator sets
[cors] ENABLED = trueinapp.ini. - Time entries cannot be backdated. Forgejo stamps its own clock at sync time and discards any date you send, so GitSheets posts a dated comment alongside each entry — that comment is the record of which day the work belongs to. See docs/FORGEJO-PLAN.md §6 for the full picture.
Forgejo tokens need read:user, read:organization, write:issue and
read:repository; read:organization is not optional, or org labels and the
picker's drill-down come back empty.
Historical note: the project was called gl-timesheet before it grew
beyond GitLab. Storage keys, backup/export kind strings, the IndexedDB
database name and the GLTS_CONNECT_SRC build variable keep their original
names on purpose — renaming them would orphan existing data and break
cross-device replication with older builds.
Setup
pnpm install
pnpm dev # http://localhost:3000
pnpm build # static bundle in .output/public (CSP injected post-build)
Quality gates: pnpm lint && pnpm typecheck && pnpm test && pnpm test:e2e
(unit + mocked-forge e2e; see tests/e2e/smoke/ and tests/smoke/ for the
env-gated suites against real backends).
Deploy .output/public to any static host. Hosts that insist on a Node
process (PM2-style panels) can instead run .output/server/index.mjs — the
build emits a zero-dependency static server for the same bundle (SPA
fallback, immutable asset caching, CSP as a real header; PORT/HOST env,
port 3000 default). GLTS_CONNECT_SRC is REQUIRED at
build time (ES-6): set it to your forge + storage origins to pin the CSP
connect-src, or build a dev/test bundle with the explicit opt-out
GLTS_CONNECT_SRC='*' / pnpm build:dev (docs/SECURITY.md). The
NuxtHub/Cloudflare D1 storage service needs a server deployment instead — see
docs/DEPLOY-NUXTHUB.md.
Connecting: token creation
- On your instance: User settings → Access tokens → Add new token.
- Select the
apiscope (the app needs to read tickets, post notes and query timelogs). Set an expiry — the app warns 14 days before it lapses. - Paste the token in the first-run wizard.
Security model (honest version)
- The token lives in sessionStorage for the browser session — never in app state, never persisted by storage adapters, never in URLs. Closing the browser forgets it.
- Remember me is opt-in: AES-256-GCM sealed under a PBKDF2 passphrase. Anyone with your device AND passphrase can recover the token, and a weak passphrase can be brute-forced offline. A forgotten passphrase cannot be recovered — you re-enter the token.
- A forge token can do anything your account can (GitLab
apiscope; Forgejo's four scopes above). The app only ever talks to the forge origins you configured and your chosen storage origin (enforced by an automated zero-egress test and the build CSP), but you are trusting this codebase with that power — read it, pin your deployment. Exception: a deployment built withNUXT_PUBLIC_GTAG_IDoffers opt-in Google Analytics — gtag.js loads only after you accept the consent banner, and never carries the token (docs/SECURITY.md, docs/DECISIONS.md). - Session comments are published to the forge at sync. Reminders sit next to every field that ends up public; still, keep secrets out of comments. Forgejo has no confidential issues at all — repository visibility is the only privacy axis there, and it is set on the forge.
- Cloud storage services have their own honest limits: the Supabase anon key and JSONBin access key act as bearer credentials for your timesheet data (the in-app setup blocks explain each model); the JSONBin Master Key is refused outright.
Catch-all ticket advice (§2.2)
Not every minute maps to a ticket. Create one catch-all issue per client or
per internal bucket (e.g. acme/admin#1 "Misc & meetings"), pin it in the
picker, and give it a billing label like any other ticket. Time you cannot
attribute precisely still lands in the right bucket instead of evaporating —
and stays correctable later (post-sync correction).
Documentation
docs/SECURITY.md— CSP, token handling, leak auditsdocs/DEPLOY-NUXTHUB.md— Cloudflare D1 deploymentdocs/ACCEPTANCE-MAP.md— CA-1..CA-26 → automated spec mappingdocs/DECISIONS.md,docs/BLOCKERS.md— engineering record (the v2.8 source spec is still unavailable; RALPH.md served as interim reference)- In-app help:
/help(five languages)