+
Bring: {p.bring}
)} @@ -196,7 +196,7 @@ export default function HowToJoin() { aside rather than a highlighted callout — it is a clarification about how the thing runs, not a selling point. */} {p.note && ( -+
@@ -241,7 +241,7 @@ export default function HowToJoin() {
across lines is ambiguous about whether the break is a newline — and 24rem is
384px, so it was silently clipped on desktop with no scrollbar visible to say
there was more. Half of 1152px is 576px, which fits it. */}
-
@@ -658,7 +656,7 @@ export default function HowToJoin() {
join makes the invitation read as selective rather than desperate. */}
Applications open Open to every SST student
- Already joined?{" "}
-
- Sign in to your dashboard
- {" "}
- — no need to apply twice.
- If you close this tab without applying If you close this tab
- You'll open it again in February. The same form, one semester
+ You'll open it again in February. The same one tap, one semester
less, and a batch of students who already know how to review your
code.
If you hit the button If you sign in
- Someone reads it this week. You show up regularly. By November
+ Somebody messages you this week. You show up regularly. By November
you're the one answering the questions.
- Open source is software written in public, by anyone, for everyone to use.
- Not a niche category — the things below are four of the most widely used
- pieces of software on earth, and you can read every line of all of them
- right now.
+ Software written in public, by anyone, for everyone. The four below are
+ among the most used on earth — and you can read every line right now.
How a change actually gets in
- {GLOSSARY.length} words that get used constantly and explained never. Not
- knowing them is the most common reason a capable person never opens their
- first pull request, and every one of us had to work them out{" "}
+ {GLOSSARY.length} words used constantly and explained never. Not knowing
+ them is the commonest reason a capable person never opens a pull request.
+ We all worked them out{" "}
by being confused in public.
What this is
- This is the part that sounds like a pitch and is not. Every line below is a
- mechanism you can trace, and the reason it works is boring: open source is
- the only part of your CV that a stranger has already{" "}
+ Every line below is a mechanism you can trace. Open source is the only part
+ of your CV a stranger has already{" "}
checked for you.
- Everything here is derived from the other four pages rather than typed in
- by hand, so it cannot say more than the evidence does. Click through and
- check any of it.
+ Derived from the other four pages, not typed in by hand — so it cannot say
+ more than the evidence does.
Signing in tells us three things, and Google is the one that tells us: the
@@ -184,7 +184,7 @@ export default function Privacy() {
+
Something here wrong, or out of date against the code? This site is one of the
club's own repositories —{" "}
diff --git a/web/app/(site)/programmes/page.tsx b/web/app/(site)/programmes/page.tsx
index 96c8439..9cc18ee 100644
--- a/web/app/(site)/programmes/page.tsx
+++ b/web/app/(site)/programmes/page.tsx
@@ -91,7 +91,7 @@ function ProgrammeField({ p }: { p: ProgrammeInfo }) {
{/* The tier, stated in words as well as carried by the colour. The
colour is never the only signal. */}
Paid open source Pick your path Upstream work
Nothing listed for this cycle yet.
Nothing published yet.
This fills in as members land work upstream. Each entry carries a link to
@@ -361,7 +361,7 @@ export default function Projects() {
argument it decorated. */}
+
Contributor counts and merge ratios were read from the GitHub API on
2026-07-29. They move — open the repository if you want today's number.
Who runs this
+
{error}
+ Computed over all {stats.total} members, not the filtered list below.
+
+ By batch, year, branch, hostel and route in. These are the only figures on this
+ page that cannot be counted in the database — batch and branch are read out of
+ each member's address rather than stored, so grouping by them means
+ loading every member.
+
+ Also switches the table below to the whole club, so search, sort and export
+ cover everybody rather than the rows loaded so far.
+
+
Batch, branch and year are read from each member's college address rather than
asked for — 23bcs10045 is the 2023–27
batch, branch BCS. An address that does not follow that pattern is counted as{" "}
@@ -418,14 +597,24 @@ export default function AdminDashboard() {
- {filtered.length} of {stats.total} shown
+ {filtered.length} of {everyone ? stats.total : (visible?.length ?? 0)} shown
{activeFilters > 0 && (
{" "}
· {activeFilters} filter{activeFilters === 1 ? "" : "s"} on
)}
+ {!everyone && stats.total > (visible?.length ?? 0) && (
+
+ {" "}
+ · {stats.total} members in total, {visible?.length ?? 0} loaded
+
+ )}
+
{v.description.length}/600
+
{error}
+
No mentors yet. Until you add one, the mentorship card on every member's
dashboard says enrolment has not opened.
{m.name}
{m.org && (
-
+
{m.org}
)}
{!m.active && (
-
+
hidden
)}
+
{labelOf(PROGRAMS, m.programme)}
+
1st: {c.first} · 2nd:{" "}
{c.second}
{m.description} {m.description}
{c.total} student{c.total === 1 ? "" : "s"} picked this mentor — hide
instead of deleting.
- {filtered.length} of {stats.total} shown
+ {filtered.length} of {enrollments ? stats.total : rows.length} shown
{activeFilters > 0 && (
{" "}
@@ -271,7 +328,7 @@ export default function AdminMentorship({
value={emailList}
onFocus={(e) => e.currentTarget.select()}
rows={3}
- className="w-full resize-y rounded-md border border-seam bg-sunk p-3 font-mono text-[0.8125rem] text-haze"
+ className="w-full resize-y rounded-inline border border-seam bg-sunk p-3 font-mono text-sm text-haze"
/>
+ Filters cover the {rows.length} loaded so far.
+
+ {stats.total === 0
+ ? "Nobody has enrolled yet."
+ : `${stats.total} student${stats.total === 1 ? "" : "s"} enrolled. The counts and charts on this page did not need any of their records; the list does, so it loads a page at a time.`}
+
+
Named rather than drawn as an empty bar, because a bar at zero is a row that
disappears. A hidden mentor with nobody is expected; a visible one is worth a
look at their description.
@@ -409,7 +519,7 @@ export default function AdminMentorship({
+
Percentages are of students enrolled, and a student holds two preferences — so the
two demand charts add up past 100%. Nothing on this page is an allocation:
preferences are what students asked for, and pairing them is still a decision
diff --git a/web/components/AppFooter.tsx b/web/components/AppFooter.tsx
index 746db70..389980e 100644
--- a/web/components/AppFooter.tsx
+++ b/web/components/AppFooter.tsx
@@ -20,7 +20,7 @@ export default function AppFooter() {
+
Privacy
@@ -40,7 +40,7 @@ export default function AppFooter() {
{/* The club, not the university. SST is where its members study; signing the
university's name to a student project would claim an endorsement nobody
gave. */}
-
+
© {new Date().getFullYear()} Scaler Open Source Club
Application received
- Somebody will message you before the next session. There is nothing else to do
- and nothing to prepare — bring a laptop.
- Open to all years, no experience needed
- One form, about a minute. No account to make, no interview, and nothing to
- prepare — the organisers read it and message you before the next session.
-
+
The grey line
@@ -154,7 +154,7 @@ export default function CommitGraph({ className = "" }: { className?: string })
+
The blue line
@@ -163,7 +163,7 @@ export default function CommitGraph({ className = "" }: { className?: string })
+
The filled dot
diff --git a/web/components/CommunityBanner.tsx b/web/components/CommunityBanner.tsx
index 7694210..f7cdaed 100644
--- a/web/components/CommunityBanner.tsx
+++ b/web/components/CommunityBanner.tsx
@@ -43,9 +43,9 @@ export default function CommunityBanner() {
const faces = people.slice(0, FACES);
return (
-
+
{2000 - body.length} characters left
{post.pinned && Pinned}
{post.archived && Archived}
-
+
{CATEGORIES.find((c) => c.value === (post.category ?? "general"))?.label}
{post.title}
+
{fmtDate(post.created_at)} · {post.author_email}
{children}
+
{EVERYDAY.map((e) => (
{m.title}
@@ -279,7 +277,7 @@ export default function Home() {
says "rebase onto upstream/main and squash before we triage". */}
+
{GLOSSARY.map((g) => (
@@ -408,7 +405,7 @@ export default function Home() {
the half that cites its cells AND stays legible at a glance. */}
+
{OPEN_ENTRY.map((p) => (
+
{PAID.map((p) => (
{["Window", "Programme", "Opens", "Start prepping", "What you do first"].map((h) => (
-
+
{h}
))}
@@ -471,7 +471,7 @@ export default function Programmes() {
that the club does not run. */}
+
{buildDay.map((p) => (
+
{clubRepos.map((r) => (
+
{upstream.map((p) => (
Breakdowns
+ Members
+ {/* SAYS WHAT IS ACTUALLY IN MEMORY. "12 of 1000 shown" while only 25 rows
+ had been fetched was the old line, and it read as a filter having hidden
+ the other 988 rather than as most of the club never having been loaded.
+ Search and sort act on what is here; the line has to admit what that is. */}
Who has enrolled
@@ -308,14 +365,14 @@ export default function AdminMentorship({
{filtered.map((r) => (
@@ -356,7 +413,56 @@ export default function AdminMentorship({
{r.name}
- {r.email}
+ {r.email}
{r.batch ? `${r.batch.label} · ${r.batch.branch}` : "—"}
{r.batch && (
- {r.batch.yearLabel}
+ {r.batch.yearLabel}
)}
-
+
{r.profile?.github ? (
-
+
{fmtDate(r.enrollment.created_at)}
Who has enrolled
+
- You're in the queue.
-
-
- Apply to join
-
- {/* NAMES THE ABSENT STEPS, because the reader's question at a form is not "what do
- I fill in" but "what happens after I do". No account to make and no interview is
- the unusual half, and it is the half that decides whether somebody starts. */}
-