UI dashboard - #6
Merged
PRAteek-singHWY merged 6 commits intoSep 4, 2026
Merged
Conversation
Adds, on top of the sign-in that already existed:
roles admins/{email} gains role + active, splitting isAdmin() from
isOwner(). Owners manage the roster from /admin; admins cannot
appoint anyone, so a compromised organiser account cannot
escalate. Both fields default permissively so rows seeded before
they existed keep working.
forms one collection for forms AND polls -- a poll is a form with
show_tally on. Responses are keyed by uid, so one-per-member is
structural rather than checked. The tally is function-written and
refused to every client.
sessions when the club meets. A real starts_at timestamp, which is the one
date in these rules not pinned to request.time, because scheduling
is the whole feature.
notices categories and a reversible archive.
The dashboards share one app shell (components/dashboard/Shell.tsx), which
replaces the marketing chrome on /dashboard and /admin -- see ChromeGate.tsx.
The profile form stopped being a gate in front of the member dashboard and
became a panel inside it; it was the reason organisers reported the site "has
no dashboard".
155 rules assertions against the emulator, plus a two-role browser pass over
both screens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both sides had independently built an app shell and a member dashboard, so
this is a synthesis rather than a fast-forward.
TAKEN FROM UPSTREAM
route groups app/(site)/ and app/(app)/. This is the textbook way to give
one set of routes a different layout, and it deletes
ChromeGate.tsx, which did the same job by checking the
pathname. A page in (app) can no longer forget the shell.
firestore.rules, firebase.json, FIREBASE.md, lib/firebase.ts
the check scripts: rules, rules-emulator, smoke, assert-site, e2e-auth
the profile model, which lost year_branch, level, programs and
programs_other -- programmes moved into their mentorship model
mentorship, onboarding, and their AdminDashboard
KEPT FROM OURS
the app shell components/dashboard/Shell.tsx is now (app)/layout.tsx,
replacing their AppHeader/AppFooter pair
MemberDashboard and its panels: the summary strip, notice board, forms and
polls, sessions, GitHub activity, where-to-go-next, details
the organisers' page composition
ADAPTED
ProfileCard and NextUp drop the fields upstream removed -- a row for a field
that no longer exists prints "undefined" at a member. NextUp loses its
programmes clause, which was the better half of that panel; re-attaching it
to the mentorship enrolment is one line when someone wants it.
ApplyForm follows LEVELS -> LEVEL_LABEL.
lib/firebase.ts gets our four collection constants back, additively.
KNOWN BROKEN, DELIBERATELY
firestore.rules is upstream's wholesale, so forms, sessions and the roster
fields have no rules and fall to the catch-all. Those panels will error.
That is a loud break, not a hole -- nothing leaks. Re-apply those blocks
before deploying.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PRAteek-singHWY
merged commit Sep 4, 2026
7641700
into
ScalerOpenSourceLabsOrg:main
1 of 2 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Joining used to end on a read-only card in the right-hand column of
/join: sevenfields, a "saved" message, and nothing after it. Everything the club wanted a member
to do next had nowhere to live. This branch builds that place, fixes two production
bugs found on the way that were silently breaking sign-in for real students, and
brings the whole page down to a size that fits a laptop screen.
Three routes instead of four states
/joinis the door,/onboardingasks three questions,/dashboardis where a memberlives. Signed-in routes are a route group (
app/(app)/) with their own layout, so noapp page can ship the marketing nav and strip it after hydration. Each route paints a
neutral card until auth resolves — treating "still checking" as "signed out" flashes a
sign-in prompt at every returning member.
Seven questions became three
year_branch,level,programsandprograms_otherare gone. The first is alreadyin the address: every member signs in as
abhinav.23bcs10045@sst.scaler.com, solib/batch.tsparses batch, branch and roll on read and stores nothing — the rulespin the address to
request.auth.token.email, so a derived value cannot be forged andcannot drift from the document beside it. The other three were a self-assessment nobody
acted on and a preference nobody read, replaced by enrolling in a programme, which is a
decision with a consequence.
Membership is not the same as signing in
isMember()used to mean "has a verified@sst.scaler.comaddress", so every student onthe domain read everything the organisers posted. That was a function name that had
stopped being true, not a decision anyone made. It is now
isStudent(), withisClubMember()a separate question answered by amembershipfield only an organisercan write. Notices, forms and sessions carry an audience —
members,studentsorboth— defaulting tobothso everything written before the field existed staysexactly as visible as the day it was posted.
What organisers get
access grant (
role+active, read by the rules on every request) and the publicbilling the
/teampage shows. They were two lists that drifted, silently in bothdirections.
npm run team:syncprojects only the public fields at build time, becauserules are per-document and the document id is an email.
organiser account cannot escalate. Both fields default permissively so rows seeded
before they existed keep working.
show_tallyon. Responsesare keyed by uid, so one-per-member is structural rather than checked, and the tally is
function-written and refused to every client.
either a second or "just my first" (exactly one, enforced both ways: "I only want
Priya" and "I have not decided" must not look alike to whoever pairs the cohort). No
capacity counter — a slot counter turns choosing a mentor into a race. Nothing here
allocates; the pairing stays a human decision.
The whole page, at three quarters
The design was drawn large enough that a 1080p laptop had to be zoomed to 75% before a
section fit on screen — which is the browser telling you the root is too big, not the
reader.
html { font-size: 75% }does what that zoom did, at every reader's defaultzoom, on the site and in the dashboard alike.
A percentage rather than
font-size: 12px, and the difference matters: a percentageresolves against whatever the reader set as their browser's default text size, so
somebody running 20px because they need to still gets their proportional increase. A
hard px value would overrule that preference outright — the accessibility failure this
looks like and is not.
What follows from that, and is the bulk of the file count:
text-[13px]-style utility was rewritten in rem (text-[0.8125rem]) so typeactually comes down with the root. This is why ~40 components are touched for no
behavioural change.
vwterms in the fluid clamps came down by the same quarter (6.2vw→4.65vw,and so on). Left in px, headings would hold their old size across the middle of the
viewport range while everything around them shrank.
minimum touch targets. A 0.75px rule is a blurry rule, and a thumb does not get
smaller because the type did.
h-14→h-[56px], it contains a 44pxtarget), so
scroll-padding-topand.page-topmoved to px too — a clearance in remwould have shrunk away from a bar that did not.
One nav, every reader
The "Sign in" link and the button beside it used to swap on
user. The bar does noaccess control and never did, so all that state bought was a control that read
differently depending on which browser you opened the site in — and a member who was
signed in lost the only door in the chrome that names the members' area. One label now,
every visit:
/dashboardshows the sign-in card to a stranger and the dashboard to amember, which is where that branch belongs.
Two production fixes carried on this branch
authDomainpointed atfirebaseapp.com. That made the sign-in handler athird-party origin; Chrome partitions third-party storage, so the credential Google
issued could not be written back and every sign-in silently failed. Two real students
hit this — accounts exist in Auth for 26 and 30 Aug, and
userswas empty. Nowsame-origin via our own
/__/auth/handler.max-age=3600. The**/*.htmlcache rule matched therequest path, and with
trailingSlasha page is requested as/join/, never/join/index.html— so it read correctly and applied to nothing. A deploy readerscannot receive for an hour is not a deploy.
**is no-cache now, with the year-longcache bought back for
/_next/static/**, where fingerprinted filenames make it correct.Team
Yash Virulkar's shadow entry gets a batch, a photo and his own words, set as prose
rather than as labels. Emoji come out of one member's highlights so the column reads as
one voice.
sarvika.jpeg(the 155KB original) is deleted now that the croppedsarvika-sharma.jpgis the one being rendered.Verification
Rules assertions against the emulator executing the rules as five different people
(
npm run rules:emulator), two browser-driven e2e suites over auth and mentorship(
npm run e2e:auth,npm run e2e:mentorship), plusqa,smokeandbrowsers. Twobugs came out of driving rather than reading:
/joinnavigated away whilesignInWithPopupwas still finalising, orphaning the Google window about one run inthree, and the app header's wordmark was a 33×28 tap target on mobile.
Also fixes a CI assertion that had been red on every push to
mainsince 23 August — itasserted one of the two states
/joinlegitimately has, and.env.localnever exists ona runner. A permanently red CI trains everyone to ignore the one signal that matters.
Notes for review
firestore.indexes.jsonadds the one composite index the app needs (announcementsby audience + date) — the audience filter cannot be done in memory, because a query
returning a refused document fails entirely rather than dropping the row. Deploy with
firebase deploy --only firestoreso it lands alongside the rules that need it.lib/audience.tsand the.get('audience', 'both')calls infirestore.rules. Change neither alone.mentors/is the first collection a client may write that is not its own row. It isadmin-only and validated anyway; a mentor entry is published, organiser-authored,
non-personal copy. That argument deliberately does not extend to
admins/.the dashboard at a default-zoom 1080p screen before merging — that is where it bites
first if it bites.