You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The clock can be scaled, so the thing this project is about can be watched instead of described. DiresQ's mechanism is the absence of an event over time — a deadline passes, nobody hears from a responder, and fifteen minutes later the server files a report about them — which is correct and completely unwatchable. DIRESQ_DEMO_SPEED multiplies the rate incident time passes: at 60, a responder goes overdue five seconds after the page loads and the report about them arrives fifteen seconds after that. Nothing about the mechanism is faked for the camera. There is one now() and every deadline, comparison and query downstream is the production code, unchanged; only the rate time enters the system is scaled. Login lockout and the ICS-214 export filename deliberately stay on the real clock, the first because scaling a security control on a public instance is not a demo feature and the second because it names a file on somebody's disk. A scaled clock forces the demo banner on and makes it state the multiplier, for the same reason the board shows when the sweep last ran: a page showing accelerated time has to say so, or the honest version of this is indistinguishable from a faked one.
The accountability board shows time remaining, not only time elapsed. Every row already said how long since we last heard from somebody, which states the past and leaves the future to be worked out. It now also says due in 5 min, counting down, and 20 min past due once it isn't. The number is in the /api/responders payload as due_in_seconds, negative once the deadline has gone.
A seeded responder whose deadline expires while you are looking at it. Everybody else on the demo board is in a state that has already settled — Sam is red, the Mayde Creek four are overstaffed, the rest have time in hand. n.farrow is the only one whose state changes on screen, because a transition needs a before, and the first attempt gave them the longest deadline on the board, so every other responder went red first and there was nothing to point a camera at. A test now asserts they lead the queue by at least three minutes, so a later change to the seed cannot quietly take the shot away again.
docs/filming.md: the arithmetic that turns a speed into a shot (red at 300/S seconds, the auto-filed report at 1200/S), why speeds above about 120 look broken rather than fast — the board refreshes every three seconds, so the countdown starts jumping by more than it counts down — and a shot list that opens cold on a red board rather than on anybody introducing themselves.
Two buttons on the map for when you have lost the incidents. Fit all frames every report currently shown — including after the coverage filter has hidden some, so it frames what is left rather than what it just hid. Next report walks through them one at a time and opens each. Both say where they went to a screen reader, because moving a map is silent.
A CONTRIBUTING.md and a CODE_OF_CONDUCT.md. The contributing guide is specific rather than generic: a fix arrives with a test that fails without it, numbers in prose have to be true because a test reads them, a weakened guarantee gets a paragraph in limits.md, and nothing that stops being true when it is written may be cached. The code of conduct is the Contributor Covenant with two additions — one rule about not putting real emergency information into a demo that cannot summon help, and an honest note that two students are not a moderation team, with a pointer to GitHub's own report-abuse flow for anything urgent.
The board now shows when the silence check last ran — "checked 2s ago" beside the live indicator, turning amber if it has not run for five minutes. The check that files a report about somebody who has gone quiet has no scheduler: it runs whenever anybody reads the board, so it cannot be a timer that dies without anyone noticing. But that was a claim in a comment, and this project does not ask anyone to take a claim about an alarm on trust. On a board somebody is watching the number always reads a few seconds, because the watching is what runs it — which is the point. You can watch it move instead of believing it.
app.py opens with an index of what is in it: fifteen sections in running order, each one a banner comment you can search for. It is one file on purpose, and the cost of that is knowing where anything is. There are no line numbers, because they would be wrong by the next commit, and a test checks the index and the file still agree — including the order.
The pages that describe the silence check now describe the version of it that exists. limits.md still says the switch depends on being run, because it does — but says that the dependency is now visible on the board rather than only in the documentation. A limitation you can see on a screen is a different claim from one you have to be told about, and the difference is worth writing down.
The README now states the total amount of writing in the project — code and documentation together — and a test holds it to the truth, counting only what somebody actually typed. The first measurement was nine and a half thousand lines too high because it counted a package lockfile, a generated model and the documentation site's copy of the documentation; there is now a test that fails if any of those creep back into the total.
Changed
docs/accessibility.md, docs/limits.md and docs/decisions.md caught up with the demo clock. The accessibility page records the fourth pass and the three things it found, and its enforcement table now lists what the new tests actually check rather than what the old ones did — including the detail that the full-stylesheet contrast audit got its light/dark classification wrong on the first attempt and was caught by its own canary. limits.md records that screen reader users do not get the countdown at all, why the alternative was worse, and that the honest fix is not written. decisions.md records why time is injectable rather than the constants being shortened in demo mode, and what stays on the real clock.
The tests that read the project's own files stopped walking into node_modules before discarding it. They were the slowest thing in the suite and are now roughly thirty times faster.
Fixed
Six stylesheets were setting real text in #6c7086 or #45475a, which are 1.4:1 to 1.9:1 against the surfaces they sit on where WCAG asks for 4.5:1 — the disclaimer's footer, the legal links under the login, signup and homepage forms, the offline page's "as of" stamp and footer, the triage footnote, and the stood-down vote and capability tags on a report. The test that was supposed to prevent this checked ten hand-listed colour pairs, so a colour used in a file nobody had thought to add to the list was never looked at. It now reads every color: declaration in every stylesheet, resolves var() against each file's own palette, and checks it against the backgrounds it can actually sit on.
The countdown on the accountability board survived exactly one render. It was added to the server-rendered template and not to the JavaScript that replaces the list every three seconds, so it appeared on load and was wiped by the first poll — invisible in the test suite, which reads the template, and invisible by hand unless you happened to watch the same row for three seconds. A test now asserts both renderers draw it. The board's coordinates and its "not assigned" / "no position" / "no contact yet" states were using --overlay on the row background, which is 1.92:1 against WCAG's 4.5:1. That grey has been banned from a11y.css by a test since it was caught on capability tags; board.css was never covered by it and now is.
The hosted demo stopped booting. The boot script asked for two gunicorn workers on a 512 MB instance, and the classifier trains when the app is imported, so each worker carried its own copy. Render prints the worker count it sized the box for — one — in its own deploy log, and passing --workers 2 silently overrode it. The container was killed four seconds after starting, and the platform then spent seventeen minutes health-checking a process that was already dead, which is why the failure reported itself as a health check that never passed rather than as a boot that never survived. The count now comes from the host.
Responders were drawn on the map in a way the legend no longer described. The legend key had been changed to a hollow ring — colour was already saying whether anyone is coming to a place, so it had nothing left to say about a person — but the map was still drawing them as solid circles in those same three colours. So the symbol people were told to look for did not exist, and the ones that did exist looked like reports. Fixing a label without fixing the thing it labels leaves a page telling two lies instead of one.
The board, the map and the feed could return an error page under load. All three run the silence check before answering, the check writes to the database, and SQLite lets one writer lock out every reader — so with a few people watching a board that refreshes every three seconds, a write could wait past its timeout and fail the whole response. The pages that exist to be watched during an emergency were the ones that broke when watched. Three changes: the database now uses a journal mode where readers and the writer stop blocking each other, the check runs at most once every thirty seconds instead of once per visitor per refresh, and a failed check is caught rather than allowed to become an error page. It deliberately does not record itself as having run when it fails, because a board showing green while nothing is checking is the worst outcome available.
A report could say somebody was on scene while the board had them forty-five minutes overdue, and the report page gave no sign of it. Status is what a responder last told us; whether they are still answering is a different fact, and only one of the two pages knew it. Anybody opening a report to decide whether the address needed more help was counting a person who had gone silent as help. The page now says how many have stopped answering, marks each of them, and says plainly not to count them. Somebody who has cleared is not chased, because going home is not going quiet.
On a phone, the only way to file a report was to open a menu labelled "Filter". The button lived in the sidebar, which slides off-screen below 768 pixels, so the one thing this app exists to do was behind the one control nobody would press to do it. There is now a Create Report button in the page itself on small screens, and exactly one of the two is ever present so a screen reader never hears it twice.
Seeded responders were on the map and could not be seen. They were placed within ninety metres of the report they had joined — close enough that the circle marking a person sat underneath the teardrop marking the place, and Leaflet draws places above people. Nothing was missing from the data, which is exactly why nothing looked wrong with it. They now stand off by about two hundred metres, so the thing this project is an argument about — several people converging on one address — is visible rather than inferred.
The demo moved every time the server restarted. Responder positions were derived from Python's built-in string hashing, which is deliberately salted differently for each process, so a seed built on the promise that every visitor arrives at the same incident produced a different incident on every boot. It now uses a checksum that gives the same answer on every machine and every run.
The map legend used the same red for nobody going and for responder, so two of its four keys said different things in the same colour. Colour was already carrying meaning — red, blue and green say whether anyone is coming — which left nothing for it to say about a person. The legend now uses shape the way the map already does: reports are teardrops, responders are a hollow ring. Colour means one thing on each.
The map stopped telling the difference between having no signal and the server returning an error, so a failure looked like being offline. It now checks the response before believing it, and the warning appears above the map rather than underneath the statistics, where a phone screen never reached it.
The stamp that records when the silence check ran could take down the page it is displayed on. It runs before every read of the board, which polls every three seconds, so something that had been read-only started writing on every request — and a write that fails inside a before-request hook returns an error page instead of a board. It now fails quietly: the timestamp goes stale and the board reports that in amber, which is true, because a check we could not record is not a check we can claim.