-
Notifications
You must be signed in to change notification settings - Fork 15
feat: crowding indicator #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
aaronbrethorst
merged 25 commits into
OneBusAway:main
from
brentonmdunn:feat/crowding-indicator
Sep 17, 2026
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
e295294
chore: add scaffolding for langauge support
brentonmdunn a979912
refactor: update message key names
brentonmdunn c2f7449
feat: occupancy formatter function
brentonmdunn 039107f
test: formatOccupancy
brentonmdunn 2fa235a
feat: edit defaults
brentonmdunn d561d3d
feat: add admin toggle
brentonmdunn 696a864
refactor: light/medium/full vals to uppercase
brentonmdunn 63582b5
feat: aadd occupancy to formatBoardDeparture
brentonmdunn cc36b33
feat: show change when occupancy changes
brentonmdunn b76755a
docs: update JSDocs for uppercase
brentonmdunn dc25af1
feat: occupancy colors
brentonmdunn 5ab996d
feat: occupancy component
brentonmdunn 0370c72
feat: show occupancy on board
brentonmdunn 4c20159
fix: adjust colors for light occupancy
brentonmdunn 2b1aac4
chore: update languages
brentonmdunn 48245c0
test: occupancy pip
brentonmdunn 95c1c2a
fix: only show occupancy when trip isn't cancelled
brentonmdunn 33f15d1
test: add check to ensure occupancy is shown and hidden when intended
brentonmdunn bf82c16
test: no occupancy status when null
brentonmdunn bd98a5f
refactor: showOccupancyStatus into showCrowding for feature name
brentonmdunn 56e840d
feat: occupancy status on multi stop boards
brentonmdunn 9f7641e
chore: update languages
brentonmdunn bf66cf8
chore: trigger CI
brentonmdunn 5899d23
chore: trigger CI
brentonmdunn f761532
Merge main into feat/crowding-indicator
brentonmdunn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { render, cleanup } from '@testing-library/svelte'; | ||
| import { describe, test, expect, afterEach } from 'vitest'; | ||
| import DepartureRow from './departure-row.svelte'; | ||
|
|
||
| function arrival(overrides = {}) { | ||
| return { | ||
| route: '249', | ||
| name: 'Route 249', | ||
| dest: 'South Bellevue Station', | ||
| min: 16, | ||
| delta: 0, | ||
| status: 'ONTIME', | ||
| departureAt: Date.now() + 16 * 60000, | ||
| tripId: 't-1', | ||
| occupancy: 'FULL', | ||
| ...overrides | ||
| }; | ||
| } | ||
|
|
||
| describe('DepartureRow occupancy', () => { | ||
| afterEach(() => cleanup()); | ||
|
|
||
| test('shows occupancy when enabled', () => { | ||
| const { container } = render(DepartureRow, { | ||
| props: { arrival: arrival(), showCrowding: true } | ||
| }); | ||
| expect(container.querySelector('.occupancy-FULL')).not.toBeNull(); | ||
| }); | ||
|
|
||
| test('hides occupancy when disabled', () => { | ||
| const { container } = render(DepartureRow, { props: { arrival: arrival() } }); | ||
| expect(container.querySelector('.occupancy-FULL')).toBeNull(); | ||
| }); | ||
|
|
||
| test('hides occupancy when there is no occupancy data', () => { | ||
| const { container } = render(DepartureRow, { | ||
| props: { arrival: arrival({ occupancy: null }), showCrowding: true } | ||
| }); | ||
| expect(container.querySelector('[class*="occupancy-"]')).toBeNull(); | ||
| }); | ||
|
|
||
| test('hides occupancy for a canceled trip', () => { | ||
| const { container } = render(DepartureRow, { | ||
| props: { arrival: arrival({ status: 'CANCEL' }), showCrowding: true } | ||
| }); | ||
| expect(container.querySelector('.occupancy-FULL')).toBeNull(); | ||
| }); | ||
| }); |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| <script> | ||
| import * as t from '$lib/paraglide/messages.js'; | ||
|
|
||
| const LEVELS = { | ||
| LIGHT: { filled: 1 }, | ||
| MEDIUM: { filled: 2 }, | ||
| FULL: { filled: 3 } | ||
| }; | ||
|
|
||
| let { level, size = 22 } = $props(); | ||
|
|
||
| const s = $derived(LEVELS[level]); | ||
| const label = $derived.by(() => { | ||
| if (!s) return ''; | ||
| if (level === 'LIGHT') return t.board_occupancy_light(); | ||
| if (level === 'MEDIUM') return t.board_occupancy_medium(); | ||
| if (level === 'FULL') return t.board_occupancy_full(); | ||
| return ''; | ||
| }); | ||
| </script> | ||
|
|
||
| {#if s} | ||
| <span | ||
| class="occupancy-{level} sc" | ||
| dir="ltr" | ||
| style:display="inline-flex" | ||
| style:align-items="center" | ||
| style:gap="{Math.round(size * 0.45)}px" | ||
| style:font-weight="500" | ||
| style:font-size="{size}px" | ||
| style:color="var(--occupancy-tone)" | ||
| style:letter-spacing="0.1em" | ||
| > | ||
| <span aria-hidden="true" style:font-size="{size * 0.95}px"> | ||
| {#each [0, 1, 2] as i (i)}{i < s.filled ? '●' : '○'}{/each} | ||
| </span> | ||
| <span>{label}</span> | ||
| </span> | ||
| {/if} |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { render, cleanup } from '@testing-library/svelte'; | ||
| import { describe, test, expect, afterEach, beforeEach, vi } from 'vitest'; | ||
| import OccupancyPip from './occupancy-pip.svelte'; | ||
|
|
||
| let mockLocale = 'en'; | ||
|
|
||
| vi.mock('$lib/paraglide/runtime.js', async (importOriginal) => { | ||
| const actual = await importOriginal(); | ||
| return { | ||
| ...actual, | ||
| getLocale: () => mockLocale | ||
| }; | ||
| }); | ||
|
|
||
| beforeEach(() => { | ||
| mockLocale = 'en'; | ||
| }); | ||
|
|
||
| describe('OccupancyPip', () => { | ||
| afterEach(() => cleanup()); | ||
|
|
||
| test.each([ | ||
| ['LIGHT', '●○○', 'Not Crowded'], | ||
| ['MEDIUM', '●●○', 'Crowded'], | ||
| ['FULL', '●●●', 'Full'] | ||
| ])('renders %s as %s with its label', (level, glyphs, label) => { | ||
| const { container } = render(OccupancyPip, { props: { level } }); | ||
| const pip = container.querySelector(`.occupancy-${level}`); | ||
| expect(pip).not.toBeNull(); | ||
| expect(pip.querySelector('[aria-hidden="true"]').textContent.trim()).toBe(glyphs); | ||
| expect(pip.textContent).toContain(label); | ||
| }); | ||
|
|
||
| test.each([null, undefined, 'UNKNOWN'])('renders nothing for %s', (level) => { | ||
| const { container } = render(OccupancyPip, { props: { level } }); | ||
| expect(container.textContent.trim()).toBe(''); | ||
| }); | ||
|
|
||
| test('keeps glyph-then-label order and translates the label in Arabic', () => { | ||
| mockLocale = 'ar'; | ||
| const { container } = render(OccupancyPip, { props: { level: 'FULL' } }); | ||
| const pip = container.querySelector('.occupancy-FULL'); | ||
| expect(pip.getAttribute('dir')).toBe('ltr'); | ||
| expect(pip.textContent).toContain('ممتلئ'); | ||
| }); | ||
| }); |
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using colors defined for a different area feels like a beige flag for me since for example "late" has nothing to do with "full" but I believe that having a shared color palette would be beneficial. There is also slight precedence with cancel not getting its own dedicated color:
waystation/src/app.css
Line 94 in 09f577c
In an ideal world, I would like INFO, WARNING, CRITICAL (or similar wording) colors defined that both status and occupancy inherit from but that feels like too big of a breaking change for agencies that customized the colors. I also don't know if there is a migration process for things like this.
Alternatives considered:
brand-*status colors are legacy codeThis is all under the assumption that a shared color palette is beneficial. I would be open to implementing 1 or 2, but they were bigger changes so I just biased towards the simplest implementation for now.