Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5a08731
Attendance: replace the pip strip with a threshold meter
CodedRichy Sep 1, 2026
50eadf7
One container treatment, and a second ground that is actually used
CodedRichy Sep 1, 2026
84d8af0
feat(ui): rebuild the header around an ask box, a bell and an account
CodedRichy Sep 1, 2026
98b3fac
feat(worker): add the Cloudflare Worker behind the ask box
CodedRichy Sep 1, 2026
9025691
fix(ui): the bell and account menu closed on the click that opened them
CodedRichy Sep 1, 2026
648e8c5
feat: sync on launch, and let notifications be dismissed one at a time
CodedRichy Sep 1, 2026
13b2701
feat: one refresh button for both portals
CodedRichy Sep 1, 2026
958a44f
feat(auth): sign in with Clerk, the way a desktop app is allowed to
CodedRichy Sep 1, 2026
51b9260
refactor(ui): extract Popover, and portal the header panels out of th…
CodedRichy Sep 1, 2026
7c05e27
fix(auth): register fixed loopback ports, and ask for two scopes not …
CodedRichy Sep 1, 2026
a810279
chore(worker): point CLERK_ISSUER at the real instance
CodedRichy Sep 1, 2026
ec6d5ce
fix(ui): the sign-in button rendered as a wrapped swatch
CodedRichy Sep 1, 2026
04e0278
fix(auth): a new sign-in releases the abandoned one's port
CodedRichy Sep 1, 2026
987e137
feat(auth): show the signed-in account, picture and all
CodedRichy Sep 1, 2026
c694d91
feat(ux): make the first run and the sign-in mean something
CodedRichy Sep 1, 2026
bd372f8
feat(home): offer the target from the card that depends on it
CodedRichy Sep 1, 2026
61f6ed2
feat(ask): route unmatched questions through the deployed worker
CodedRichy Sep 1, 2026
2aff0dd
fix(worker): route on gemini-flash-lite-latest, and stop the palette …
CodedRichy Sep 1, 2026
9b56219
fix(palette): match a sentence's words literally, not as subsequences
CodedRichy Sep 1, 2026
c0ef7d0
fix(history): a blank box is not a delete, and a typed figure is not …
CodedRichy Sep 1, 2026
368f98a
feat(palette): match screens on the words the stop list throws away
CodedRichy Sep 1, 2026
510c6cb
feat(engine): price the next absence, not just the eligibility slack
CodedRichy Sep 1, 2026
782ca5a
feat(worker): log every question the router is asked, content apart f…
CodedRichy Sep 1, 2026
5b16526
feat(ask): answer the question in the box instead of pointing at a sc…
CodedRichy Sep 1, 2026
8c8448c
feat(attendance): show the counts every percentage is computed from
CodedRichy Sep 1, 2026
0f82f56
feat(ask): answer marks, standing and plain attendance questions too
CodedRichy Sep 1, 2026
f1f50b5
feat(ask): name the assistant Tex, and let it answer for itself
CodedRichy Sep 1, 2026
f5f39b5
feat(palette): the search pill grows into the bar, and shrinks back
CodedRichy Sep 1, 2026
4aa2e4c
fix(home): stop reporting a confirmed score of zero as an all-clear
CodedRichy Sep 1, 2026
2a1c121
test(worker): cover the layer that holds when the prompt and the sche…
CodedRichy Sep 1, 2026
b60a84c
chore(release): 0.3.0
CodedRichy Sep 1, 2026
4f2e097
fix: dead external links, the Wednesday overlap, and docs that had go…
CodedRichy Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,33 @@ jobs:
fi
echo "parity.json unchanged since 9b2bdc2"

# The Worker is deployed separately and was outside CI entirely, which meant
# `parseAction` - the last of the three topic restrictions, and the only one
# that runs on code we control rather than on a remote service's behaviour -
# had nothing checking it on any push.
worker:
name: worker types and tests
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5

- uses: actions/setup-node@v5
with:
node-version: 20
cache: npm
cache-dependency-path: worker/package-lock.json

- run: npm ci
working-directory: worker

- name: Typecheck
run: npm run typecheck
working-directory: worker

- name: Tests
run: npm test
working-directory: worker

rust:
name: rust
runs-on: windows-latest
Expand Down
101 changes: 101 additions & 0 deletions .impeccable/critique/2026-09-01T06-22-44Z__app-src-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
target: app/src/ui
total_score: 28
max_score: 40
na_heuristics:
p0_count: 0
p1_count: 4
timestamp: 2026-09-01T06-22-44Z
slug: app-src-ui
---
Method: dual-agent (A: design review · B: detector + screenshot evidence)

## Design Health Score — 28/40 (Good)

| # | Heuristic | Score | Key Issue |
|---|---|---|---|
| 1 | Visibility of System Status | 2 | Ledger edits commit silently; only save feedback fires on FAILURE |
| 2 | Match System / Real World | 4 | Near-perfect; only the 14 raw column codes leak |
| 3 | User Control and Freedom | 2 | No undo anywhere; one hover-click deletes a subject + all its marks |
| 4 | Consistency and Standards | 3 | Four "selected" idioms; drawer tabs styled by inline styles, outside tokens |
| 5 | Error Prevention | 3 | Strong where present, undermined by unconfirmed row delete |
| 6 | Recognition Rather Than Recall | 3 | Glossary is one of three mutually exclusive tabs |
| 7 | Flexibility and Efficiency | 2 | Zero keyboard shortcuts; no sort/filter/search on 14-column grid |
| 8 | Aesthetic and Minimalist Design | 2 | Two class names draw one identical container; one elevation level |
| 9 | Error Recovery | 3 | Excellent copy, then a raw alert() in Data.tsx:565 |
| 10 | Help and Documentation | 3 | Glossary exists only on the Semester screen |

## Design Specificity Verdict

Authored for a KTU tracker, not reskinnable. Brand hue chosen because its job is status (H:178, clear of danger/warn/good). Mono justified ("every number is a mark"). Glossary is regulation text. No Inter, no purple gradient, no three-card grid.

Deterministic scan: 0 findings on app/src/ui + index.html, BUT those scans never evaluated CSS (.tsx is regex-mode; HTML shell has no styles). Direct stylesheet scan found 3: two side-tab left-stripe borders, one un-waived overused-font (Space Grotesk waived only for a scratchpad doc path, not the app). No visual overlays; CLI-only.

## Overall Impression

"Everything is a card" is TRUE BUT LOCALIZED. Rendered container counts: Data 8, Home 6, Subject detail 2, Ledger 1, History 1. Ledger and History are already card-free (the 1 is the shared global notice). Disease is concentrated in Data and Home.

Mechanism: .card and .tile are THE SAME DRAWING under two names — identical gradient border-box, identical --surface-1, identical 1px solid transparent. Differ only in radius (14px vs 6px) and padding. motion.css already treats them as one. A `card` grep undercounts by half: 8 .card + 5 Home tiles.

Underneath: tokens define a 3-step elevation ramp; --surface-3 is used 3 times in the whole app (scrollbar thumb, hover, holiday stripe), never for content. --surface-1 carries every container. Hierarchy system built, one level used.

The fix already exists in-repo: .claims is a borderless hairline-ruled list whose comment says "The grid version is the reflex and it flattens an argument into a brochure." Never propagated past one component.

## What's Working

1. Refusal to invent a number, enforced at every render site (dashes not 0.00, "no completed semester yet", withdrawn semesters excluded and said out loud). This is the trust position of a tool competing with the university's own portal.
2. Progressive disclosure via native details — keyboard/SR behavior free, styled as text affordance not competing with card primary action.
3. Measured color with a failing test behind it — --text-faint lifted 2.95:1 to 4.64:1; comments catch OKLCH values outside sRGB being silently clipped.

## Priority Issues

[P1] Two class names, one container, one elevation level.
Why: Operate mode = scanability. Data renders 8 identical rectangles; Home 5 tiles whose only differentiator is one amber border the CSS forbids reusing. Hierarchy falls entirely on typography while Home already spends two .huge figures + a --text-2xl stat + a gauge. Four focal points, one surface, no ranking.
Fix: collapse .card/.tile into one class with modifiers; convert Data's seven ops and Attendance's subject list to .claims-style ruled rows; PROMOTE the one or two real subjects per screen to --surface-2 so two levels exist.
Command: /impeccable layout

[P1] Attendance is three screens stacked on one scroll.
Why: screen titled "How many classes can you miss?" but the day calendar and weekly timetable answer neither that nor each other. Student scrolls past two 820px-min-width tables to leave.
Fix: segmented control — Budget / Day by day / Timetable — using the existing .seg pattern.
Command: /impeccable layout

[P1] Day-by-day calendar invisible to screen readers.
Why: cal-block is a bare div with a title and NO text content inside an otherwise-empty td. Browse mode yields an empty cell; no text equivalent anywhere. title is sole carrier in five places app-wide.
Fix: real (visually-hidden) text per cell — "Mon 18 Aug, period 3, absent". Never title alone.
Command: /impeccable harden

[P1] Destructive delete, no confirm, no undo.
Why: .del invisible until hover, one click removes subject + every mark, and it is the LAST TAB STOP on every row. Recovery is "you exported a backup earlier."
Fix: optimistic removal + "Removed PCCST501 · Undo" 10s in the existing notice slot.
Command: /impeccable harden

[P2] Real text defects from screenshots.
- Truncation: "Design and Analysis of Algorit…" in COURSE column (dark AND light); same name renders full in Home tiles, so the column is the constraint.
- Home "Needs attention": description column ~10-14 chars; breaks across 5 lines, one holding just "be".
- Drawer spacing: ATTENDANCE VS INTERNALS ~17px below preceding paragraph vs ~50px above sibling GOAL — attaches to wrong thing.
- Light mode: card borders near-invisible against cream ground; only the amber-flagged tile reads as bounded.
Command: /impeccable polish

## Persona Red Flags

Alex (power user): zero keyboard shortcuts (one onKeyDown app-wide). white-space:nowrap makes a 14-col row wider than viewport, so tabbing scrolls the table sideways under him; sticky header does not help because the COLUMNS move. Target grade is a select per row. No sort/filter/search.

Sam (screen reader): strong baseline (sr-only announcements, type-enforced labels, scope on every header) then — calendar unreadable, .drawer-tabs is a tablist wearing aria-pressed (nothing announces the panel changed), expanded row is td colSpan=14 holding a 7-input form.

Jordan (first-timer): Home's SECOND tile says "12 CIE marks lost" while CIE is defined only in the drawer's THIRD tab. Data offers 5 import routes with nothing marked as first choice, though Setup has a .route-tag and Home says the portal is fastest. "SET CODE" is dev language in caps. Drawer tabs: only the third is in her vocabulary and it is placed last.

## Minor Observations

- Three corner radii visible on Home at once: 14px tiles, 6px cards, 3px notice.
- colSpan={14} is a magic number 280 lines from the thead it must match.
- About a dozen inline style color var(--text-faint) where .faint already exists.
- screens.css is 957 lines; header still says "Screens beyond the ledger".
- Pointer-tracked spotlight fires on 2 of Home's 5 tiles, implying the other three are inert.
- Light and dark are pixel-position identical; only palette swaps.

## Questions to Consider

1. The ledger is where students live (flat hairlines, 3px radius); Home, read once a day, gets 14px corners, gradient border, spotlight, staggered mount. Which screen deserves the design budget?
2. Attendance appears three times (ledger column, Home tile, whole screen). What if it were a MODE you flip the table into rather than a fourth peer view?
3. The .claims comment already names the failure mode. So what made .card the default everywhere else?
4. The app will not print a number it does not know. Is it equally rigorous about not drawing a container it does not need?
14 changes: 11 additions & 3 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,28 @@ Verify the download first — see **Signing** below.

## What it talks to

Three hosts, and only three. There is **no telemetry, no analytics and no
crash reporting**; the only `fetch` in the entire frontend is the second row of
this table (`app/src/state/actions.ts`).
Five hosts, and only five — and the last two are reached only if a student
chooses to sign in, which nothing else in the app requires. There is **no
telemetry, no analytics and no crash reporting**.

| Host | When | Why |
|---|---|---|
| `github.com` | A few seconds after launch | Update check — reads `releases/latest/download/latest.json` |
| `raw.githubusercontent.com` | On launch | Course catalogue refresh, so a KTU curriculum revision does not need a new build |
| **Your own portal** | Only when the student presses Sync, and only to the address they typed | Reads attendance and marks |
| `*.clerk.accounts.dev` | Only if the student signs in | Sign-in, which gates the question box and nothing else |
| `targetx-ask.*.workers.dev` | Only when a signed-in student asks a question the app could not answer locally | Returns a destination — a screen or a subject — never a figure |

Nothing else. If your network blocks GitHub, the app still works: the update
check and the catalogue refresh both fail quietly and the bundled catalogue is
used. Portal sync is optional — everything can be typed or pasted in.

**Blocking the last two rows is supported and costs nothing but the question
box.** No marks, attendance or CGPA are sent to either host; what leaves is the
question text and the student's course codes and titles. If you would rather
students could not sign in at all, build from source with `VITE_CLERK_CLIENT_ID`
left empty and the feature is absent rather than merely blocked.

### About the portal

The student's portal password is held in a local variable for the duration of
Expand Down
41 changes: 38 additions & 3 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# Privacy

TargetX is a desktop application that runs entirely on the machine it is
installed on. There is no TargetX account, no TargetX server, and no telemetry.
TargetX is a desktop application that runs on the machine it is installed on.
Every figure it shows is computed there. There is no telemetry, and no account
is needed to use any of it.

One optional feature is different, and is set out in full below: the question
box can forward a question it could not answer locally to a server of ours, and
that is the one part that asks you to sign in. It never receives a mark, an
attendance figure or a CGPA. If you never sign in, nothing in this paragraph
applies to you and the rest of the app is unaffected.

This document describes what the software actually does, and every claim in it
can be checked against the source in this repository. Where a claim is about
Expand All @@ -24,7 +31,8 @@ different one.

## What is sent over the network

TargetX makes network requests in exactly three situations, and no others.
TargetX makes network requests in exactly four situations, and no others. The
fourth happens only if you choose to sign in.

1. **When you press Sync.** It signs in to the college portal URL that you
typed, and reads your attendance and internal marks. This is a request to
Expand All @@ -36,6 +44,33 @@ TargetX makes network requests in exactly three situations, and no others.
3. **A few seconds after launch, to ask whether a newer version exists.** This
is a request to GitHub's release feed. It carries no information about you
beyond what any file download carries. See `app/src/sync/update.ts`.
4. **When you are signed in and ask a question the app could not answer by
itself.** This is the only request that reaches a server of ours, and the
only one that requires an account. Most questions never get here: attendance,
marks, standing and the regulations are all answered on your own machine,
offline, and only a phrasing the app does not recognise is forwarded.

**What is sent:** your question, and the code and title of the subjects you
are registered for — the course list, so the question can be matched to a
subject.

**What is not sent:** marks, attendance, CGPA, your name, your register
number, your password. None of it. See `app/src/state/ask.ts`, which is the
only file that decides what goes into that request.

**What comes back is not an answer.** It is a destination: one of five
screens, or one of the subjects you sent. The reply is parsed into a fixed
type before the app acts on it, and anything outside that type — including a
subject code you did not send — is rejected. There is no field in it that
could carry a figure about you, so the model cannot state one even if asked
to. See `worker/src/schema.ts`.

**What is logged:** the question text, the outcome, how many subjects were
sent, and how long it took — so the app can learn which phrasings it failed
to answer locally. Not logged: any account id, any token, or the course codes
themselves. Identity and content are deliberately kept apart, so the log is a
record of what people ask rather than a record of what a named student asks.
See `worker/src/log.ts`.

Your marks are never sent anywhere, by anyone, for any reason. There is no
endpoint that would receive them.
Expand Down
Loading