Changelog
Straight from the repository — this page is the release notes, not a retelling of them.
Changelog
All notable changes to LibTrack. Versions follow semantic versioning:
MAJOR.MINOR.PATCH.
Installing a newer version over an older one is safe — the database lives in the app's userData
folder, separate from the install directory, and schema migrations run automatically on first launch.
1.34.0
Two reports from using the app, and they turned out to be one idea: the Sessions tab had become a second, worse Scanner. It is the attendance history, and history is not where you run today's class.
Fixed
The session running right now can no longer be edited. This was not cosmetic. Editing a session's date shifts every one of its attendance timestamps by whole days — so editing the live session moved that morning's scans onto another date, at which point the app could no longer find the session as live while the Scanner carried on writing into it. The rest of the class landed in a session dated last week and nothing anywhere said so.
Refused in the main process, not merely hidden on screen. End the session in the Scanner and it becomes editable immediately — the block is on the live session, not on today.
A class session from a past date that was never ended is untouched by this and stays editable. It is not live; it is what Time out all exists to repair.
The Active badge is read from the database, not from the window drawing it. A session started at the self-service display used to look like ordinary history in the admin window — and therefore offered to edit itself.
Changed
- "Start session" is gone from the Sessions tab. It remains on the Dashboard card and the Scanner's idle screen. Three entry points into one workflow was one too many, and the one on the screen whose whole subject is the past invited running the class from the archive.
- Time out all is disabled on the live session, for the same reason as Edit: ending a class from the history screen is the confusion this release is about. It stays available on the Scanner, which is where a class is ended, and on past sessions that were never closed.
Both controls are disabled with a reason rather than removed — a button that vanishes reads as a broken screen, and a header whose buttons come and go moves the next one under your pointer.
32 suites, 1,509 checks.
1.33.0
Auto-update has worked since 1.31.0 and was confirmed on a second machine on 12 August 2026. It was also completely invisible: updates downloaded in the background and applied on the next ordinary close, and there was no way to know an update existed, was downloading, or was waiting — short of quitting and watching the version number.
That silence was the right default for a school PC and the wrong one for anybody who wants to know.
Added
- A Software update card in Settings — the current version, what the updater is doing right now, a Check for updates button, and Restart & update. Available to every signed-in user, not just admins: closing LibTrack at four o'clock already applies a pending update, so hiding the button would only hide whether it is going to happen.
- A Restart & update button in the top bar, which appears only once an update has finished downloading. It confirms before restarting.
- Download progress. "Downloading version 1.34.0… 43%" — the difference between installing and stalled on a slow connection, which was previously unknowable from outside.
The top bar had carried no buttons since 1.14.1
That rule removed a theme toggle and Start/End session, on the grounds that a control which repaints the app or writes a record should not be a stray click away on every screen. This button is an exception for reasons rather than by erosion: it does not exist unless an update is genuinely ready, an update being ready is exactly the kind of global status the bar already carries (the live session dot, the Demo data chip), and it confirms before doing anything. It is rendered at all times and hidden, so the day an update lands it does not shove the status line sideways.
A separate rule — that a drag region swallows keyboard focus for its whole subtree, which once broke
typing on the login screen — does not apply: button is already exempt from the drag region. A text
field in the top bar would still be a bug, and there is not one.
Where the safety lives
The restart is refused during a class session, with the self-service display up, or if the request comes from the display's own window. None of those are decided by the screen showing the button — the main process reads them for itself: the live session from the database, the display from the window list, and which window asked from its webContents id. A guard that the guarded window can fill in is not a guard.
mayInstallNow and its tests were written in 1.31.0, before there was anything to call them, so that
these conditions were decided while nobody was in a hurry. This release is their first real use.
Two new behaviours worth naming: offline is now said out loud here, unlike everywhere else in the updater — someone who pressed a button is owed an answer, even though an unsolicited toast every morning would be noise. And a development build says so instead of offering a button that would throw.
32 suites, 1,485 checks.
1.32.0
Reported from the desk: start a class session, scan students in, and the dashboard's Inside now does not move. It was right about that, and the cause was worse than a stale number — there was no "who is inside" query at all.
The dashboard counted the open rows in one session: today's walk-in log. A class scan writes its attendance row under the class session, so a class of forty checked in was counted nowhere. The self-service display had the mirror-image bug: while a class ran it showed the class register and hid every walk-in visitor standing in the room. Two screens, the same label, two different populations, neither of them the library.
Fixed
- Inside now counts everyone inside. Walk-ins, class attendees, faculty and guests, across every session of the day.
- The self-service display shows the same number, whether or not a class session is running.
- The dashboard keeps up. It fetched once when you opened it and never again — no timer, no refresh — so a scan at the entrance did not appear until you navigated away and came back. It now checks every 15 seconds, and immediately when a session starts or ends.
- Somebody in both registers is one person. Walk in at 09:00, get scanned in with your class at 09:30, and you hold two open records. That is honest — nobody watched you leave the first, and inventing a departure time is exactly what this app refuses to do elsewhere — so both are kept and the screens show you once. Timing out at the display closes both, rather than leaving you on the list you just pressed the button in.
- A section marked absent is not "inside the library". Absent students carry a row with no time in and no time out; the query requires a time in, so they are excluded.
- The session history's open column counts visitors too. It read the student table only, so a walk-in day whose remaining visitors were a parent and a teacher reported nobody left inside.
Added
- A Register column in Currently inside, and the same label on the display, reading
Walk-inor the class period. Once the two lists are one, "why is this student here when the Walk-in tab does not list them" has to be answerable on the screen showing them.
Not changed, on purpose
- What a scan does. The display still writes to the class session when one is live and to the walk-in day when none is. Only what it shows was wrong; changing where an unattended entrance files a check-in is not a thing to do while fixing a count.
- Visits today and Recent walk-ins still count walk-in traffic, because that is what they say they are.
Two new suites, smoke-insidenow.mjs and smoke-insidenow.cjs, take the total to 32 suites and
1,443 checks. The first assertion in the second one is the reported sequence, and it fails against
the old code.
1.31.0
The librarian designs the school's ID card in Canva and sent the export. LibTrack printed something else entirely: a barcode, a name and a section in a plain bordered box. This release prints her card.
Added
- Upload your school's own ID card artwork (Settings → ID card design), then drag LibTrack's fields onto it — name, section, ID number, serial and barcode — on a preview at the size it actually prints. Everything is measured in millimetres, because the card is a physical object and a percentage layout would silently change every measurement the moment the artwork was re-exported at a different pixel size.
- A back side, for the terms and conditions most school cards carry. It has no per-student fields — it is the same for everybody — and it prints as a second sheet with the columns mirrored, so a duplex run lands each back behind its own front.
- Faculty cards use the same template for free:
facultyCardsalready reshapes staff into the record the card reads, with the designation where a student's section goes. - The plain built-in card stays, and stays the default. It is the only one that fits the 70×30 and 50×25 sticker presets — a full-bleed card design cannot be shrunk onto a spine label.
Why upload rather than build the design in? Because rebuilding her Canva file in code would look approximately right and be wrong the moment she edits it, which she will every school year. The artwork is an image LibTrack never interprets; only the field positions are the app's business.
The four things that would have gone wrong
Each of these looks perfect on screen and costs a print run:
- A barcode printed on textured artwork does not scan. Her card is marble throughout, and a CODE_128 symbol needs a white quiet zone and contrast. The barcode field always renders on an opaque white plate, whatever is underneath.
- The background is embedded once, as a single CSS rule. Measured on her real artwork: 500 cards come to 0.99 MB. Inline per card the same sheet would be about 191 MB — a PDF nobody can send and a preview that never finishes rendering.
- The ragged last row must be padded before it is mirrored. One card in a two-wide row still occupies a column, so reversing the short row on its own puts it behind a different pupil's front. Only discoverable after the sheet has been guillotined.
- Blurry artwork is refused before it is stored, below 250 dpi. Her export is 1011 × 639 over a CR80 — exactly 300 dpi — and a screenshot of a Canva tab is not.
Changed
toSVGinsrc/lib/barcode.jstakes astretchoption. A barcode in a box of someone else's proportions must fill the width it was validated at; the default fit would scale the whole symbol down to the shorter axis and quietly take the bars under the minimum width.src/lib/cover.jsgainsartworkFromFile, beside the book-cover pipeline it shares its guards with. Card artwork keeps the school's original file byte for byte when it is small enough — re-encoding to JPEG at 700px, which is right for a photograph of a book jacket, would be about 200 dpi and would ring around the hard edges a designed card is made of.scripts/smoke-cardtemplate.mjs(new, 79 checks) andsmoke-layout.mjs40 → 55. 30 suites, 1,366 checks.
1.30.0
LibTrack had no way to be seen without being installed, and no way to be updated without someone being sent a file. This release is the distribution story: a website, a demo that is the actual application rather than a video of it, and updates that arrive on their own.
Added
- A website —
site/. Static Astro: landing page, download, privacy, terms, a docs scaffold, and a changelog page rendered straight from this file so the two cannot disagree. Deployed to Vercel; the installer itself stays on GitHub Releases, which is also the update feed, so the download button and the updater point at one artifact. - A playable browser demo, at
/demo/. Not a recording and not a mock-up:src/db/sqljsdriver.jspresents better-sqlite3's surface over SQLite compiled to WebAssembly, sosrc/db/database.jsandsrc/db/circulation.js— 4,550 lines — run unmodified in a browser tab against seeded sample data. All 19 database suites pass against that driver as well as the native one (943 checks either way), which is what makes the demo worth trusting as a preview of the product instead of a separate thing that resembles it. The database is built in memory when the page loads and is gone on refresh; nothing is uploaded, because there is nowhere to upload it to. - Silent auto-update. Downloads in the background, applies on the next ordinary close. Offline is treated as the normal state it is — every failure is a log line and nothing reaches the screen, because a librarian cannot act on "could not reach GitHub" and a daily toast only teaches people to dismiss toasts. The check starts after the window is up and never on the startup path, so a school with no internet, or with a captive portal that hangs rather than refuses, opens exactly as fast as it did before.
npm run verify:demoandnpm run verify:site— drive the built output in real Chromium, sign in through the actual login form, walk every screen, and fail on console errors, broken links, missing images or sideways scroll at phone width. They also capture the screenshots the site ships.
Fixed
- The installer filename and the update feed disagreed. electron-builder's default template
produced
LibTrack Setup 1.29.0.exewhilelatest.ymladvertisedLibTrack-Setup-1.29.0.exe— the spaces come from the template, not fromproductName. Publishing to GitHub hides this by uploading under the sanitised name, but a feed hosted anywhere else sends the updater after a file that does not exist and the update fails silently, with nothing wrong at build time and nothing wrong in the running app.build.artifactNameis now pinned andsmoke-feedname.mjsasserts the built file and the feed are the same string. - README and SECURITY.md claimed the app "makes no outbound connections". That has been untrue
since the catalogue shipped —
books.lookupIsbncalls openlibrary.org — and auto-update makes it more so. Both now state exactly what leaves the machine and when. Neither request carries personal data, and a claim of none is falsified by one counter-example, after which nothing else on the page is believed either.
Changed
- The renderer's API surface moved out of
electron/preload.jsintosrc/lib/bridgesurface.cjs, shared by the desktop bridge and the demo's. One definition instead of two that drift — and the copy that drifts is always the one nobody is looking at. smoke-bridge.cjs(new) asserts every method the bridge names exists indb.api, and every bridge pathsrc/lib/api.jsreaches for exists on the bridge. Neither direction had any test: a typo in preload used to surface at runtime, on whichever screen used it, as "Unknown IPC error".
1.29.0
Preschool sections are routinely staffed by two teachers at once, and the app made that unsayable: one teacher per section, enforced by a unique index.
Added
- Several teachers on duty per section. Both halves of Start session take a list now, not one name — as do Add past session and Edit session. The mode labels changed with them: "Same teachers for every section" and "Different teachers per section", since the difference between the two is no longer how many teachers you may name.
- The teacher picker is searchable. Type part of a name instead of scrolling
a dropdown of forty. It ranks surnames first, the same way every other
person-search in the app does, so
ocampofinds Ms. Ocampo even though faculty are stored first-name-first. - Reports and the session history show every teacher on a section, joined into one cell.
Fixed
- The monthly Excel export would have double-counted a double-staffed
section.
reports.detailand the absences report joinedsession_dutydirectly, so two teachers on a section returned every attendance row twice — and those sheets are what a month's figures get counted from. Both now aggregate the names in a subquery, so one pupil is one row whatever the staffing. Caught before release; no shipped version could produce it, because no shipped version could record two teachers. sessions.dutywould likewise have listed a class once per teacher on the Scanner's breakdown.
Internal
- The duty picker is one component (
src/components/DutyPicker.jsx) shared by all three dialogs rather than three near-copies. The copy that would have gone un-updated is Edit session, where the cost is not an awkward screen but silent data loss: opening a double-staffed session in a single-teacher editor and pressing Save replaces two teachers with one. - The two partial unique indexes now carry
teacher_id, and the old ones are dropped on upgrade. Left in place they would keep refusing the second teacher — with a rawUNIQUE constraint failed— which is the shape of migration bug that only appears on installs that have been running for a year. smoke-duty.cjs63 → 78 checks, including the export row-count.
1.28.1
Reported from a library running the self-service display on a smart TV, so preschool teachers could reach it. The TV stopped accepting input: the taskbar became unreachable and the keyboard with it, and unplugging the television was the only way out.
Added
- Close the self-service display from the Dashboard. The card that launches it now has a Close display button beside Launch, and says which state it is in. No password: the password guards the prompt on the display, and this exists for when that display cannot be used at all.
Fixed
- The display could only ever be closed from the display itself. It is fullscreen, frameless and refuses Alt+F4 — deliberately, so nobody at the entrance can dismiss it — which left one route out, sited on the one thing that had failed. A control surface with a single instance, mounted on the device that breaks, is no control surface at all.
- Closing the admin window while a display was running left LibTrack alive and
unreachable. The display still counted as a window, so
window-all-closednever fired, and the window that could have closed it was gone. Only Task Manager could end it. Closing the last real window now quits, as it always did before a second window existed.
Internal
electron/kioskcontrol.js— both decisions above as pure functions, testable without opening a window, for the same reasonauthz.jsis pure. A kiosk still cannot close itself through the new channel: the display closing is exactly what the entrance password protects, since the signed-in admin session is behind it. Verdicts come from which window asked, never from what it sent.
1.28.0
Everything here came from one library, and three of the four reports were workarounds for the same gap: there was no way to change many faculty members' designation at once.
Added
- Bulk selection on the Faculty screen. Tick several people and set their designation in one go, or remove them together. The actions sit in the filter bar itself, in the gap beside the search field — and they are always there, merely hidden, because a group that appeared on the first tick would take its width from the designation chips and re-wrap them under the cursor. Admin accounts cannot be ticked, and the server refuses them even if something else asks — the renderer is not a security boundary.
- Removing several faculty asks for the count to be typed. The number is the thing most easily got wrong, especially with a search filter active, so confirming means having read it. Sessions, library visits and loan history all survive: those records name people from their own snapshot.
- The faculty CSV now updates people already on the roster instead of skipping them. Rows match on Faculty ID first — the stable key, since names get retyped — then on name. The preview separates will add, will update and already correct, so a file that changes nothing looks like one.
Fixed
- The Designations dialog showed "0 people" against categories five people held, while the filter chips beside it showed the true counts. Both numbers were correct arithmetic over different data: the chips counted the roster the screen had just loaded, and the dialog read a count computed in SQL and cached in a list that is only refreshed when a designation changes — never when the roster does. Importing a CSV therefore left it frozen at whatever it was when the admin signed in. There is now one count, derived where it is displayed. A number that is never stored cannot go stale.
- Re-uploading the faculty CSV failed with "already in roster". The importer could only ever add, which made a spreadsheet useless for the job it is best at. Its other effect: people imported before their designation existed could never be given it in bulk — re-uploading the same file now completes them.
- A blank cell in the CSV no longer erases what is there. The file reached for to fix designations often carries only name and designation columns.
- A row carrying someone else's Faculty ID is refused, naming who holds it, rather than quietly rewriting that person's record.
- Deleting a faculty member or admin who had logged attendance or assigned
duty failed with a raw
FOREIGN KEY constraint failed. Two columns pointing atteacherswere never cleared; onlysessions.teacher_idwas. Found while building bulk removal, which makes it far easier to hit.
Internal
src/lib/designationcounts.js— the one counting function, withscripts/smoke-designationcounts.mjs(22 checks) as the regression test.ConfirmTypedmoved fromSettings.jsxintoui.jsx; three near-identical destructive dialogs is how one ends up quietly missing its password field.- A CSV still cannot grant a login. The update path is a new way to reach an
existing admin row, so
username,password_hashandroleare excluded by a fixed whitelist and the suite imports a file carrying those columns to prove nothing moves.
1.27.2
Changed
- Surname matches come first on every roster you search, not just in Add past session: the Students screen, a section's roster, Faculty, the Walk-in person picker and the self-service find your name. Two of those change what actually happens rather than just the order — pressing Enter in the Walk-in picker checks in the top match, and the self-service list only shows the first six students it finds, so the person searching could previously be cut off the end of a list that had found them.
- The "matched on first name, ID or section" divider is gone from Add past session. The count beside the search box still says how many of the roster are showing.
Fixed
- Searching by student or faculty ID now works. The search was matching an internal record number
instead of the card number, so typing an ID off a card found nothing — while a short query like
1quietly matched a third of the school. This affected Add past session, where the search box has offered to match an ID since 1.27.0. - Faculty surnames now rank as surnames. Teachers are entered first-name-first with a title —
Ms. Shaira Ocampo— where students are stored surname-first, so the ranking read the whole name as the surname andocamponever sorted above someone's first name. Titles and suffixes likeJr.are no longer mistaken for the surname either.
1.27.1
Changed
- The Catalogue's List / Shelf switch is a pair of icons, a rows glyph and a grid of squares in a single segmented control, instead of two words in bubbles. Both carry a tooltip and announce which one is active, since an icon on its own says nothing to a screen reader. Its width no longer depends on which of the two words is longer.
- The book list scrolls with the page, like the shelf does. It was capped at 560px and scrolled inside its own box, so the two views behaved differently and the list sat in a frame within a frame. There is now one scroll region on the screen — the page's, which already reserves its scrollbar. A side effect worth knowing: the column headers now stay in view down the whole list, not just the first screenful.
Fixed
- The Catalogue's columns no longer move when you filter it. Column widths were derived from
whichever books were on screen, so clicking through the A–Z strip slid Author, ISBN, Category and
Copies across by hundreds of pixels — under Q the Author column began at 573px, under M at
1005px. The widths are now declared, with the title column the only one that flexes. A title too
long for its column ends in
…and shows in full on hover, and rows are all the same height.
1.27.0
Added
- Search the roster in Add past session. With six sections picked the list runs to hundreds of names, and finding the two who were absent meant scrolling all of them. Searches surname, first name, ID and section. It filters what is shown only — every student in every picked section is still recorded.
- Surname matches come first. Names read Surname, First M., and first names repeat heavily, so
a plain search for
delmixed Del Rosario in among everyone called Delia. Surname matches are now grouped at the top, with a line marking where they end and what the rest matched on — searchingana, which is nobody's surname, no longer looks like the search misfiring.
Changed
- Add past session is now a landscape dialog. The setup — date, times, teacher on duty, sections — is on the left, and the class roster fills its own column on the right. Below a narrow window the two stack as before, with the roster given a fixed height rather than whatever is left over.
- Search boxes clear with an × inside the field, on the Sessions list and the session detail as well as in Add past session. The Clear button beside the sessions list is now a small × beside the date fields, clearing only the date range — the × in the search box would have been claiming to do less than it did if it also wiped the dates. It also closes most of the empty gap at the right-hand end of that bar: the button's space is held whether or not it is showing, and an icon holds far less of it than a text button did.
Fixed
- The roster no longer collapses when several sections are picked. With Multiple teachers on duty selected and six grade levels ticked, the per-section teacher list took enough of the dialog that the student roster was squeezed to a sliver and its heading was clipped at the bottom edge. It was the same fault as 1.26.4: every control above it had a fixed height and the roster was the only flexible one, so it absorbed all of the difference. The two columns cannot take height from each other.
- A form error message can no longer be squeezed in a fixed-height dialog — it was the one shared block in the dialog body without a reserved height.
1.26.4
Fixed
- Nothing in a toolbar resizes anything else any more. Clicking an A–Z letter in the Catalogue
changed the width of the search box and the category picker, because
6 title(s)becoming26 title(s)widened the count beside them and those two flexible controls gave up the pixels. The count now has a width its contents cannot change. Same fix in the Sessions list, the session detail, the walk-in log and the Sections roster. - The search field no longer resizes while you type in it. In those same bars a Clear button
appeared on the first keystroke and the count re-worded itself from
45 visit(s)to3 of 45— both of which moved the field being typed into. The button is now always part of the layout and merely hidden until there is something to clear. - Look up in Add Book no longer narrows the ISBN field while it runs, from the label changing to Looking….
- The remaining eight scroll areas reserve their scrollbar, finishing what 1.26.3 started: dialogs, the import preview, the print preview, the borrower and book pickers, the kiosk list, the designation list and both duty lists. The pickers are the visible one — results narrowing past the fold used to step the rows sideways as you typed.
- A toast raised while a dialog is open is now readable. It sat below the modal backdrop, so any message — including a failure — was invisible whenever a dialog was up.
- A confirmation always draws on top of whatever raised it. All overlays share a stacking level, so order decided, and a confirm raised from the Start-session or Change-password dialog would have appeared underneath it.
Added
scripts/smoke-layout.mjs(23 checks) — reads the stylesheets and fails if a scroll container has no reserved gutter, if a reserved-width utility has lost its last call site, or if the toast drops below the modal overlay. The eight missed scroll areas were each written months apart and none was wrong on its own, which is exactly the kind of drift a sweep catches and a checklist does not.CLAUDE.md— the layout rule these releases exist to enforce, and the three questions that catch a breach while the code is being written rather than after the wobble is spotted. The mechanical half is the suite above; this is the half only a person can apply.
1.26.3
Fixed
- The screen no longer shifts sideways when a page starts scrolling. Clicking through the Catalogue's A–Z strip nudged the whole layout by a few pixels on every letter that held enough books to need a scrollbar, and back again on the letters that did not. The scrollbar's width is now reserved whether or not one is showing, so the page is the same width either way. Same fix for the in-panel tables, where filtering a long list past the fold moved every column.
1.26.2
Added
- Sample faculty data, for trying the CSV import without inventing a staff list:
sample-data/faculty-40.csv— a plausible school roster, 40 people, imports clean.sample-data/faculty-messy.csv— one row per importer behaviour: an unknown designation, a designation given by its code, a blank one, a duplicate inside the file, a name already on the roster, a faculty ID already taken, an ID reused within the file, no ID at all, an empty name, and a name withñ, an accent, a hyphen and a comma in a quoted field.sample-data/faculty-alt-headers.csv— the same records under the headings a school's own list arrives with (Teacher Name,Employee ID,Department…).
sample-data/README.mddocuments the expected result for every row, andscripts/smoke-sampledata.cjsasserts all of it — so if what you see disagrees with the table, one of them is a bug.
Notes
faculty-messy.csvcarriesusername,passwordandrole=admincolumns on purpose. Faculty do not sign in and the importer does not read those columns — the row exists so the test can prove it, by checking the imported person has no username, cannot sign in, and that those credentials do not log in.- Faculty IDs start at F-0101, clear of the four the demo seed creates. Colliding would make every row error on a duplicate ID and the file would prove nothing.
- The generator gained
--faculty, which writes just these three files and needs no network — fixing a typo in the staff list should not mean forty throttled Open Library requests first. It runs off its own PRNG, so adding faculty does not rewrite the committed book and student files.
1.26.1
Internal only — no change to how anything behaves.
Changed
- The Excel row-building moved into its own module (
src/lib/reportrows.js), leavingexporter.jswith just the workbook writing and the download. Every sheet's shape — which columns it has, what a missing value renders as, which populations get a sheet of their own — is now covered by tests.
Notes
- This closes the gap behind the designation bug fixed in 1.23.0. That map went stale in 1.20.0 and shipped for three versions because nothing could test it: it sat behind ExcelJS and a browser download, so it was never tested, and a school with a custom designation simply got a column of dashes. The new suite fails on exactly that symptom — verified by putting the bug back and watching it break.
- Also now asserted: a waived fine reads as owing nothing, the catalogue summary's total row actually matches the sheet beside it, every recording method has a label (including By hand), absences carry no time columns, and every sheet is rectangular — a ragged one silently loses cells when written.
1.26.0
The last of the test-run list.
Added
- Import faculty from a CSV. Faculty tab → Import CSV, with the same pick → preview → confirm flow as the student and book imports: every row checked before anything is written, mistakes editable in the preview, and a downloadable template. Columns: name (required), faculty_id, designation, subject, email.
- A row naming a designation that does not exist yet is flagged and still imported — the person is real, and their name is the part that matters. The preview offers Create N designations, after which those rows pick it up without re-choosing the file.
- Bulk-clear a section. Tick students on a section's roster and Remove from section — for emptying a class at the start of a school year.
Notes
- Removing students from a section does not delete them. They stay on the roster with their attendance, loans and history intact and simply become unassigned, so last year's reports stay true. Deleting them would take their attendance with them and quietly rewrite the year that just ended, which is why bulk deletion is deliberately not offered — the single-student Remove is still there when someone really has left.
- The header checkbox selects what is visible. With a search active, one that quietly selected the hidden rows too would be the difference between clearing four students and clearing forty.
- A CSV cannot create a login. Username and password columns are not read at all — faculty do not sign in, and a spreadsheet is the worst possible place for a credential.
- Duplicates are caught both against the roster and within the file: the same name twice is a transcription slip, not a request for two records.
1.25.0
The Self-Service Display.
Added
- The check-in sound works on the display. The chime, or the spoken surname, now plays there on the way in and the way out — it was already built and simply never wired up. It follows the same Settings toggle as the rest of the app; visitors chime without a name, as they do at the desk.
- Teachers can start a class session from the display. A Teacher: start a class session button asks for a scanned faculty card — not a password, not a name off a list. The card identifies who is on duty, and only someone holding theirs can start one. Add an activity name and go.
- While a session is running the display says so across the top, because it changes what a scan means: during a session a scan is class attendance; the rest of the day it is a library visit.
Changed
- The display returns books but no longer lends them. A return needs nobody present — the book's own
label identifies the loan — which is what suits an unattended window. Lending required pulling a
borrower up first, which is exactly the state a passer-by could walk into and borrow against. Books go
out at the desk, in front of someone.
loans.checkoutis off the display's capability list entirely, so this is not a hidden button.
Fixed
- A class session no longer disappears if the app is closed mid-class. Which session is live was only ever remembered in the admin window; now it is read from the database — today's class session with no end time — so re-opening the app walks straight back into it, and both windows always agree.
Notes
- "Live" means today. A session left unended three weeks ago is not current — it is history for Time out all to tidy. Treating it as live would silently file today's scans into last month.
- One live class session at a time, refused with a clear message rather than quietly creating a second. Two would split one class across two records with no way to merge them.
- The display cannot end a session, edit one, or create one any other way — starting by card is its only route, and it always means today, now.
1.24.0
Added
- Search inside every attendance log, in three places:
- Walk-in — today's log, by name or ID.
- Scanner — the running session, by name or student ID, stacking with the section chips so "who in 7-A is called Cruz" works.
- Sessions → any session — that day's record, class or walk-in, by name, ID or section.
- Each shows the times already in the row, which is the point: confirming whether someone was in the library and when, without scrolling a class of forty.
Notes
- These are filters over what is already on screen, not a search across the whole database. Finding a visit on a particular day means opening that day's session — say the word if you want one box that searches every day at once.
- The footers keep reporting the session's real size and add "showing N" when a search is on. A count that shrank with the search box would misreport how many attended.
- The Scanner's search appears as soon as anything is logged, unlike the section chips, which still need two sections before they mean anything.
1.23.0
Added
- A monthly absence report, on the Reports tab under the charts. Every absence for the selected month with the context needed to act on it: student, section, activity name and the teacher who was on duty for that student — no cross-referencing three other screens to find out what happened.
- It has its own search box, so "how often was this student absent" is one question rather than an export.
- Absences now get their own sheet in the monthly Excel export, beside Guests and Faculty.
Changed
- The Absences figure at the top of Reports now counts the selected month. It used to read "this session" — the live count from whatever session happened to be running — sitting in a row of yearly figures under a month selector, so it changed when a session started and read 0 the rest of the time.
Fixed
- Faculty designations in the Excel export. The exporter kept its own private list of JHS/GS, which stopped being right in 1.20.0 when designations became yours to manage — a school that added "SHS Faculty" got a column of dashes in every export with nothing saying why. It now reads the same list as the rest of the app.
Notes
- An absent student has no times at all — that is what absent means here — so absences are their own report and their own sheet rather than rows in the check-in detail. Padding a times sheet with blanks would corrupt any pivot table built on it, which is the same reason guests were split out.
- Absences are not counted as library traffic anywhere: not in the monthly totals, not in the per-grade report, not in the Detail sheet. Somebody who did not come did not visit.
- Marking a student present afterwards removes them from the report, and correcting a session's date moves its absences with it.
1.22.0
Labels, from the test-run list. No behaviour changed.
Changed
- "Period" is now "Activity name", and the list of Period 1…6 suggestions is gone. The field was always free text, but a dropdown of periods made it read as a fixed set and pushed everything into "Period X" phrasing — so Reading time or Grade 7 research looked wrong. Type whatever the visit actually is. Your last one is still remembered, and it no longer starts pre-filled with "Period 1".
- Teacher-on-duty options are now "One teacher on duty" and "Multiple teachers on duty." They mean exactly what they did before — one teacher covering the whole session, or one per section for merged classes — the old wording just did not say so.
Notes
- Sessions already recorded keep whatever they were called; nothing was rewritten. A session saved as "Period 1" still reads "Period 1".
1.21.0
First of several releases working through the test-run list. This one is Sessions.
Added
- Edit a logged session (admins). Corrects its date, activity name and teacher on duty from the session's own page. Who attended is not changed here — that is a different and much bigger job.
- Time out all. On a running session it times everyone out without ending it, for a class that steps out or a run of forgotten check-ins. On a past session that was never ended, it closes the leftovers. Walk-in days are excluded: each visitor left at a moment nobody wrote down.
- Search the sessions list — one box across the date, activity name, every teacher on duty and the sections that attended, plus a date range. The list also gains a Sections column, since "the one where 7-A and 7-B came in" is how a session is usually remembered.
Changed
- The red "N not timed out" badge is now plain grey text reading "N open". It stops nagging, and it stays visible because it is the only way to find the sessions that Time out all exists to fix.
Notes
- Moving a session's date moves every check-in with it, keeping each person's time of day, so the session and the monthly report still agree afterwards. The reports count by when a student checked in, not by the session's date — correcting one without the other would have left the two disagreeing with nothing on screen to reveal it. The dialog says how many records will move before you save.
- Absent students have no times to move and never acquire any.
- An edited session is marked "Edited on …" in its detail, for the same reason a hand-entered one is marked: history that can change silently is worth less than history that says when it changed.
- Nothing times people out automatically — not at midnight, not on the next launch. It never has. If the app is closed mid-class those check-ins stay open until someone uses Time out all, which is why the count stayed on the list rather than being removed outright.
1.20.0
Added
- Designations are now yours to manage. Faculty tab → Designations (admins only): add as many as your school needs, rename the ones that are there, remove any you do not use. JHS Faculty and GS Faculty were hardcoded into the app until now — one school's vocabulary that nobody else could change.
- Each one shows how many people hold it, so removing one is a decision rather than a guess.
- The Faculty tab's filter chips are built from the list, so a new designation gets its own chip and count immediately, and it appears in the Add/Edit dropdown.
Notes
- Renaming is safe. What is stored against a teacher is a stable internal code, not the label, so correcting "JHS Faculty" to "Junior High Faculty" keeps every person tagged and simply shows the new name — on the roster, in the walk-in log, on the session detail, on the self-service display and on their printed ID cards.
- Removing one keeps the people. They become unassigned and stay on the roster with everything else intact — the same way removing a section keeps its students. The confirmation says how many are affected before it happens.
- Upgrading changes nothing you can see: an existing install comes up with the same two it already had, and a designation you delete stays deleted across restarts.
- A teacher who holds a designation you later removed keeps working normally — they check in, borrow books and appear on the faculty report; their tag simply reads a plain "Faculty" until you give them a new one. Opening their record shows the old value marked (removed), so saving another field cannot silently wipe it.
- Admin accounts still carry no designation. An admin is an operator of the app, not a member of faculty.
1.19.0
Added
- Enter a past session by hand. New Add past session on the Sessions tab (admins only) records a class visit the app did not run — from a paper logbook, or from before the app was installed. Pick the date, the times, who was on duty and which sections attended; everyone is present unless you tick them absent, the same order the live roster works in.
- It fills the hole in your reports. The monthly totals, the per-grade report and the Excel export all count by when a student checked in, so a correctly dated entry lands in that month's figures rather than this one. That is the real point of the feature, not the list itself.
- Hand-entered records are permanently marked. A By hand chip in the session list, the date the entry was made in the session detail, and By hand in the Method column of every row — including in the Excel Detail sheet. Typed-in attendance and observed attendance must never look identical.
- A warning, not a block, when the date already has sessions: it names what is there so you can tell your own earlier entry from a genuinely different period.
- Hand-entered sessions can be deleted and re-entered. Sessions the app actually recorded cannot be deleted from the Sessions tab — that is observed evidence, and there is no undo.
Changed
sessions.removeis now admin-only. Deleting a session takes all its attendance with it, and until now any signed-in user could call it. It was only ever reachable from the discard-an-empty-session path; now that the Sessions tab can reach it, it belongs with the other record-reshaping actions.
Notes
- Every rule below is enforced in the app's core, not just in the form, and a refusal writes nothing — no session, no attendance, no duty rows: a date in the future; an end time at or before the start; a student's time in or out falling outside the session; the same student twice; an empty roster.
- An absent student gets no time in and no time out, always — even if times are supplied. "Still inside" means a time in with no time out, so a stray time in on an absent row would make someone who was not there read as someone who never left.
- The times recorded are the ones you enter. This needed a new method: the normal check-in path stamps the current moment unconditionally, so a back-dated session created through it would have held check-ins timestamped today.
- Walk-in days cannot be back-filled. Each visitor arrives and leaves at a moment nobody wrote down, so the times would be invented — the same reason ending a session never times out walk-ins.
1.18.0
Added
- Faculty can be checked in at the desk. The Walk-in tab's search now offers faculty alongside students, each tagged as what they are — a section badge for a student, JHS Faculty / GS Faculty for a teacher. One tap, no form. The visit is recorded as a faculty visit, never as student attendance, and it gets its own sheet in the Excel export exactly as before.
- The Walk-in scanner reads faculty cards too. Both the camera and the USB scanner try a student ID first and a faculty ID second, so one scanner handles both and whoever is holding a card does not need to know which sort it is. Scanning someone who is already inside times them out.
- Faculty ID cards can be printed. New Export ID cards on the Faculty tab, using the same dialog, sizes and print/ZIP as the student cards — including the guard that refuses a batch whose barcodes would be too thin to scan. Faculty had a "what their card scans as" ID since 1.12.0 with no way to produce the card.
Changed
- Checking a faculty member in BY NAME now needs the staff unlock, exactly as a student does. Scanning a card is still never gated, at the desk or at the self-service display — holding the card is the proof. Guest sign-in stays outside the lock, which is deliberate: a guest row names a stranger with no record here and grants nobody attendance credit, while typing in a faculty member asserts something about a named person on the roster.
- The Dashboard's Currently inside table now shows what each person is. It has always included faculty and guests — it reads the walk-in log — but drew only a section badge, so a teacher inside the library appeared as a row with an empty cell. Their ID column was blank for the same reason.
- The walk-in and dashboard tables' first column is headed Person, not "Student". It has carried guests and faculty since 1.12.0.
Notes
- Most of this already existed on the self-service display, which has resolved both kinds of card since 1.13.0. This brings the staff desk level with it rather than adding a third way of doing it.
- A faculty member with no Faculty ID can still be checked in by name; they are simply left out of a card batch, and the export names them rather than printing a short sheet without saying so.
- Removing a faculty member keeps their past visits with the name snapshot, as before. Those visits then read as guest visits, since "faculty" is derived from the link that removal clears — the alternative is deleting them, which would rewrite the library's traffic history.
1.17.1
Changed
- The self-service display launches from the Dashboard. It was four cards down the Settings screen, which is the wrong place for it: opening the entrance window is something you do at the start of a day — next to starting a class session — not a preference you set once. It now sits in the Dashboard's right column with the same admin-only restriction and the same explanation of what the window can and cannot do.
- Settings keeps a one-line pointer inside Walk-in security, because that card is where the manual-entry lock is set and the two are one decision: the lock is what protects an unattended entrance window. A pointer rather than a second Launch button, so the action has one home.
1.17.0
Added
- Choose the teacher on duty when a class session starts. Starting a session now asks, instead of writing whoever pressed the button. Either one teacher for the whole session, or one per section for merged classes that each brought their own — ticking a section prefills its own adviser, so the usual case is two ticks and Start.
- A section that turns up unannounced can be assigned there and then. If a student scans in from a section nobody named, the Scanner's new On duty bar shows it as a gap with a picker beside it. No need to end the session to fix the record.
- The session history shows the split. One teacher shows as their name, several as "3 teachers", and opening the session lists every section with its teacher and its own scanned/present counts.
- The Excel Detail sheet gains a "Teacher on duty" column — per student, the teacher who was supervising their section in that session. Kept separate from "Logged by", which is the account that entered the row; those are frequently different people.
Changed
- The period is a real field. Every session ever recorded was saved as "Period 1" because the value was hard-coded, so the history's Period column has always said that. The start dialog now asks, offers the usual values, accepts anything typed, and remembers your last choice.
Notes
- Nothing about merged sessions changed. Three sections with three teachers is still ONE session, ended once, counted once, and closed with a single time-out for everyone — the per-section teacher is an indicator on top of that, not a split.
- A section-specific teacher overrides the session-wide one, so "Ms. Cruz has the session, except Mr. Lim has 7-B" records correctly everywhere, including in the export.
- Naming nobody is allowed. A session that could not start because a dropdown was empty would be worse than a session with a gap the breakdown shows you.
- The history's "Teacher on duty" column shows
—for sessions recorded before this version. They never held a duty teacher — that column was showing the admin who opened them, which is the bug being fixed. Who operated the app is still recorded per check-in and shown in the session detail's "Logged by" column. - Removing a faculty member keeps the sessions they were on duty for; their name stays readable from a snapshot taken when the assignment was made. Renaming a section carries its duty records with it.
1.16.0
Added
- An A–Z strip in the Catalogue. A row of letters sits centered under the search box and the
category dropdown; click one to see only the titles starting with it, and click it again to go back
to everything.
#collects the titles that start with a number or a symbol. Finding a book you can half-remember the name of no longer means typing it. - Letters holding no books are dimmed and unclickable, so a click never lands on an empty screen. The strip re-counts as you search or change category, and stays accurate while a letter is chosen.
Notes
- A title files under its literal first letter: The Little Prince is under T, not L. No list of articles to maintain, and where a book lives never surprises you.
- Case makes no difference — a title typed in lowercase files under the same letter as one typed properly.
- The letter narrows Labels and Export too, exactly as the search box and category already do: both cover what is on screen, and both now say which letter in their description.
- The letter is a filter, not a preference — unlike List/Shelf it is not remembered, so coming back to the Catalogue never shows you a quietly narrowed shelf.
1.15.0
Added
- Create missing sections straight from a student import. When a CSV names sections that do not exist yet — a new school year's roster, typically — the import preview now says which ones and offers a single Create N missing sections button. Those rows move from Unassigned to Will add immediately, so the students land in the right section on the first import instead of needing to be reassigned afterwards.
- The new sections are deliberately unfinished but correct: the code from your file is used as the
name, the adviser is left unset, and the grade level is filled in only when it can be read from
the code with confidence (
G7-A→ Grade 7). Give them proper names, grades and advisers in the Sections tab whenever suits — nothing depends on doing it first.
Notes
- The grade level is never guessed at.
JHS-1andSampaguitaare left blank rather than being called Grade 1, because a blank grade is a visible gap while a wrong one quietly misfiles a whole section in the per-grade report. - Two spellings of the same section (
G7-Aandg7-a) create one section, and a value that already matches an existing section's code or name is left alone. - Sections have always been creatable without an adviser, and one can be attached later — this simply uses that. Removing that person afterwards still leaves the section and its students intact.
1.14.1
Changed
- The top bar is now status only. It showed the live-session state and carried three buttons on
every screen — a theme switch that repaints the whole app and a button that starts a database
record, both one stray click away no matter what you were doing. The buttons have moved to where
the thing they act on lives:
- Dark theme → Settings → Appearance. It is also remembered now; it used to reset to dark every time the app started.
- Start session → the Dashboard card, the Scanner's idle screen, and the Sessions tab. All three already existed; only the top-bar copy is gone.
- End session → the Scanner, the screen the session is actually run from. It appears there only while a session is live.
- Removed three settings that did nothing. "Dim absent students in the log", "Notify teacher on duty" and "Require login for sessions" saved your choice and were then read by no code at all — and the last was impossible anyway, since teachers stopped having logins in 1.12.0. A switch that silently does nothing is worse than one that is not there.
1.14.0
Fixed
- Ending a class session left everyone without a time out. Time Out and Duration were empty for every class check-in, in the session history and in the Excel export. Ending a session used to change nothing in the database at all — it only cleared the screen. It now times out everyone still in the session, all with the session's end time, since a class leaves together. The toast says how many: "Class session ended · 24 student(s) timed out at 10:45".
- Ending a session now asks first, and says what it is about to do: how many check-ins are still open and will be timed out, how many students never checked in and are unaffected, and that the session cannot be reopened afterwards.
- A student who never checked in is not given a time out. They never arrived, so a departure time would be invented. (A student who did scan in and was later marked absent keeps their time in, so they are timed out — their record is completed, and they stay marked Absent.)
- A student timed out individually before the session ended keeps their own earlier time.
- If ending a session fails, the session now stays open rather than clearing the screen as though it had worked.
Added
- Sessions record when they started and ended. Both columns existed but were never written.
- A session that was never properly ended is now flagged in the history — closing the app mid-class leaves check-ins with no time out, and by the next launch the real end time is unknowable. Rather than guess one, the list marks the session "N not timed out" and the detail view shows No time out on those rows. It no longer reads as a class where everyone left instantly.
Notes
- Walk-in visits are deliberately unchanged. A visitor who forgets to sign out leaves at a time nobody recorded, so nothing is stamped for them — the log still shows Inside today and No time out on a past day, which is the truth. Only class sessions have a shared end.
- Existing sessions in your history keep their empty Time Out. Nothing back-fills them, because the time they ended is not recoverable.
1.13.1
Fixed
- The self-service display had minimise / maximise / close buttons, and its ✕ closed the admin window rather than the display — invisibly, since the display covers it full-screen. Those buttons are gone from the display entirely.
- Staff exit appeared to shut the whole app down. It was not the exit: the admin window had already been closed by that ✕, so closing the display left no windows and the app quit. Exiting self-service now always returns you to LibTrack, reopening the admin window if it was closed.
- The window buttons on the admin window now act on the admin window in all cases. They previously acted on whichever window the app considered "main", which was only ever correct when one window was open.
Security
- The display cannot be closed with Alt+F4 or Ctrl+W. The only ways out are staff exit with an admin password, or quitting LibTrack from the admin window — so leaving the display unattended at the entrance no longer depends on students not knowing a keyboard shortcut. Windows shutdown and Task Manager are unaffected; the app will never block your machine from restarting.
1.13.0
Added
- Self-service display. Settings → Self-service display → Launch opens a full-screen window for the library entrance that needs no sign-in. Students and faculty scan their card to check in, scan again to check out, and can borrow or return books on their own. Returning a book needs no card at all — just scan the book.
- It shows who is currently inside, and rolls over to a new day by itself if left running overnight.
- Closing it requires an admin password, so a student cannot casually shut the display down.
Security
- The self-service window is restricted in the app's core, not just in its screens. What it may do is decided by which window the request came from, matched against a fixed allow-list, so the restriction holds even if the display's interface were tampered with. It cannot edit, delete, export, view reports, change settings or take backups — only check people in and out and lend books.
- Typing a name at the display is locked by default, exactly as it is on the Walk-in tab. An admin can unlock it at the display itself with their username and password; it re-locks on the same timer (Settings → Walk-in security). Scanning a card is never blocked.
- Attendance recorded during an unlocked window is credited to the admin who unlocked it. A scan at the unattended display records no operator, which is the truth.
1.12.0
Changed — please read before updating
- Teachers no longer have logins. Setting up a username and password for every teacher was
friction for something no teacher needed. Teachers are now a faculty roster: a name, a
designation, and a card number. On update, every existing teacher account is converted to a
faculty record and can no longer sign in — only
admin(and any other admin account) can. Their old username becomes their Faculty ID, so cards already printed keep working at the circulation desk. - Teachers → Faculty in the sidebar. Adding someone now takes a name; the designation and card number are optional, and account fields appear only if you deliberately tick "this person is an admin".
Added
- JHS Faculty / GS Faculty designation, a fixed choice rather than free text so the roster stays filterable. The Faculty screen filters and searches by it.
- Faculty check-in. Faculty can be checked in and out of the library like anyone else. They are recorded as faculty, not as guests and not as student attendance — so they never affect section counts, the per-grade report, or visitor totals, and they get their own sheet in the Excel export.
- Section adviser. A section can name the faculty member handling it, chosen from the roster instead of typed. Removing that person leaves the section intact, just without an adviser.
- Search within a section. The student list in a section now has a search box that filters live by name or student ID as you type, with a count of how many matched.
Notes
- The update rewrites the
teacherstable to make credentials optional. Existing sessions, attendance records, library loans and fines all keep pointing at the right people —scripts/smoke-faculty.cjsbuilds an old database and checks exactly that before and after. - Faculty records can hold no password even if one is supplied programmatically; credentials are stored only for admin accounts.
- The app now refuses to remove or downgrade the last account that can sign in.
1.11.0
Changed
- Two merged classes can now be marked in one session. The section dropdown on the Scanner did two jobs at once — it chose which section the "Mark all present" buttons applied to, and filtered the table. So marking Grade 10-A, then switching to 10-B to mark that too, made 10-A disappear, which looked as though a session could only hold one section. They are now two separate controls: pick any number of sections to bulk-mark, and filter what you are looking at independently. Marking never changes what is shown.
- The section filter now lists only the sections actually in the session, with a count on each, and the footer says how many sections are present.
Notes
- Nothing about the data changed, and no migration is needed. A session was never tied to a section —
each attendance row has always carried its own student's section — so scanning two classes into one
session already worked. Only the toolbar suggested otherwise.
scripts/smoke-multisection.cjsnow pins that behaviour down. - Sessions started before this version are unaffected and display normally.
1.10.0
Added
- Open a loan to see the whole record. Click a book's title on the Loans screen and a details window shows the cover and full catalogue entry, the copy's barcode, who has it (with their section or role), when it went out and which staff member issued it, when it is due and what fine is accruing right now — plus everything else that borrower has out and any unpaid fines they owe. Their other loans are clickable, so you can walk through a borrower's whole shelf without leaving the window.
- Renewals are now recorded. A loan carries how many times it has been renewed, when, and by whom, shown in the details window. Nothing is blocked — a book can still be renewed as often as needed — but a copy that has been quietly renewed ten times no longer looks the same as one borrowed yesterday.
Changed
- Renew now asks first, and shows both dates. It used to move the due date the instant you clicked, with no undo — renewing recomputes the date from today, so the original was simply gone. The confirmation names the book and the borrower and shows the current due date alongside the new one. On an overdue copy it also says how late the book is and that renewing does not cancel a fine already accrued.
- Renew and Return are available inside the details window as well as on the row. Both go through the same confirmation.
Notes
- Existing databases gain the renewal columns automatically on first launch. Loans made before this version read as never renewed, which is all that can honestly be said about them.
- The date shown in the confirmation is a preview; the date recorded is whatever the app computes at the moment you confirm. These differ only if the day changes while the dialog is open.
1.9.1
Fixed
- Unticking a row removed it from the import preview entirely, with no way to see it again or tick it back on — and the row count dropped as though the file had shrunk. Skipped rows now stay exactly where they were, greyed out and marked Not importing, with their own filter button. They still say what was wrong with them, so using "skip all error rows" no longer throws away the reasons.
- Switching filters left a blank space below the last row. The list scrolled back to wherever you had been, which was often past the end of the shorter list. It now returns to the top.
- The selected filter button showed coloured text on its coloured background, which was hard to read.
- The student importer called duplicate IDs "Skipped", which now collides with genuinely skipped rows — they are labelled Duplicate, which is what they always were.
Notes
- Skipped rows are still excluded from the import itself, exactly as before. Only what the table shows changed — the plan behind it is unaltered, so a skipped row still cannot affect the rows around it.
1.9.0
Added
- The import preview is now a working screen, not just a report. Both CSV importers (books and students)
gained the same treatment:
- Find the rows that matter. Filter buttons across the top — New title / Adding copies / No change / Error — with a count on each. Importing 1,000 books and being told "2 unchanged" used to mean scrolling a thousand rows to find them; now it is one click.
- Fix a bad row in place. Click a title, author, ISBN or copy count (name, ID or section for students) and correct it. The preview immediately re-checks the whole file, so the verdicts stay accurate — fixing one row can change another's, and the numbers update to match.
- Skip rows you don't want, individually or all the error rows at once. The totals follow.
- Save corrected CSV — after fixing rows in the preview, your original file is still wrong. This writes the corrected version out so the next import starts clean.
Fixed
- The preview table's column headings scrolled out of sight, leaving several columns of unlabelled numbers. They now stay pinned.
- The dialog had two scrollbars and showed about five rows at a time regardless of window size. It is now a single, much larger panel that uses the height available.
Notes
- Nothing about how an import behaves has changed — the same rules decide what happens, and the preview is still generated by the importer itself rather than guessed at by the screen, so it cannot promise one thing and do another.
1.8.1
No product changes. Adds sample-data/ — 1,000 book records and a 500-pupil roster for trying the CSV
importers, and for demonstrating LibTrack with a catalogue in it rather than an empty one. The book records
are real ones from Open Library, so the Look up button in Add Book resolves against them; the roster is
generated. Not included in the installer. See sample-data/README.md.
1.8.0
Added
- Import books from a CSV. Catalogue → Import loads an existing inventory — the spreadsheet the
library already keeps — instead of typing books in one at a time. Columns are
title(the only one required), plus author, isbn, publisher, year, category, copies and notes. Common heading spellings are recognised, so Book Title, Qty, Genre and No. of Copies all work without editing the file first.- You see a preview of exactly what will happen — which titles are new, which will have copies added, which are already correct — before anything is written.
- ⚠️
copiesmeans how many copies you hold, not how many to add. A book already in the catalogue has its copies topped up to that number. So a file saying 5 for a title you hold 3 of adds 2, and re-running the same file adds nothing. - Nothing is ever removed. A file listing fewer copies than you hold is reported and ignored — copies can carry borrowing history, and a spreadsheet should not be able to erase it.
- Existing book details are never overwritten. A matched row adds copies and nothing else, so a file with the author's name spelled differently cannot quietly rewrite your catalogue.
- A book is matched on its ISBN, or on title + author when it has none. Admins only.
- Export the catalogue, in two formats for two different readers:
- CSV — the same columns Import accepts, so it loads straight back in. Edit holdings in Excel, or take your data elsewhere.
- Excel — an inventory report with copies held, available and on loan, plus per-category totals, for handing to an administrator.
- Both cover whatever the search and category filters are currently showing, the same as Labels.
Fixed
- Column headings containing a hyphen were silently ignored on import —
ISBN-13,First-NameandStudent-IDwere all dropped, and the column simply appeared empty. This affected the student importer too, since it shares the same header matching.
Internal
- Two developer scripts were repaired.
smoke-grade.cjshad been failing at its own setup since students gained separate first/last name fields, so the per-grade report and the schema migration it covers were going unverified.seed-months.cjscreated students without those name fields — they displayed correctly everywhere, but opening one for editing showed empty name boxes and it could not be saved again without retyping.
Notes
- Importing 2,000 titles takes well under a second. (Copy barcodes used to be allocated with a full table scan per copy, which would have made a large import crawl; they are now allocated in one pass.)
- CSV files are written with a UTF-8 byte-order mark so Excel on Windows reads Filipino names correctly — without it José and Parañaque arrive mangled.
1.7.0
Added
- See the sheet before you print it. "Print sheet" is now Preview & print — ID cards and copy labels open in a preview showing the actual page, at the actual proportions, with an estimated page count. Windows' print dialog says "This app doesn't support print preview" and there is no way around that from inside the app, so LibTrack now provides its own.
- Save as PDF, from the same preview. Worth preferring over printing directly: a PDF has no "Fit to page" setting to get wrong, so the millimetre sizing the whole sheet depends on survives into the file — and the file can be emailed to a printing shop rather than printed in-house.
Fixed
- Scanner → Print produced a near-blank page. The app had no print stylesheet at all, so the printer received the dark theme's near-white text on white paper, with the sidebar and top bar included and the attendance log cut off after roughly one screenful. Printing now gives black on white, no navigation furniture, the whole log across as many pages as it needs, and column headers repeated on each page.
Notes
- The preview shows the page margins, so the sheet on screen matches the printed page — including how many cards actually fit across a row. (Page margins are a print-only concept, so a preview that ignores them quietly lays cards out across a wider page than the printer will use.)
- The print dialog is titled with the name of the running program. The installed app has always titled it LibTrack; it reads "Electron" only when running from source during development.
- LibTrack now identifies itself properly to Windows, so it groups under its own taskbar entry instead of a generic Electron one.
1.6.1
Changed
- The app starts faster. Everything in the interface used to be loaded and compiled before the window could paint — one 2 MB file, including the Excel writer, the charting library and the camera scanner, even if you only wanted to sign in. Screens now load when you first open them, which takes the startup work down to roughly a tenth of what it was. The first visit to Reports, Settings or the Catalogue in a session may pause very briefly; every visit after that is instant. Nothing about how the app behaves has changed.
- The camera scanner loads only when a screen is actually switched to camera mode. Desks using a USB barcode scanner — the recommended setup — never pay for it at all.
Notes
- No database, feature or setting changes. Internally the 84 KB
screens.jsxwas split into one file per screen, and the prototype files inarchive/(plus some stray development leftovers) were removed; they remain in git history.
1.6.0
Added
Book covers. Open a book in the Catalogue and choose an image file. Covers appear as thumbnails in the book list, in the new Shelf view, on the book's own page, and at the Circulation desk when a copy is scanned — so staff can see at a glance that the right book went out.
- Images are resized to 700px and re-saved as JPEG, so a 6 MB phone photo is stored as roughly 120 KB.
- Photos taken sideways are rotated upright automatically.
- Books without a cover show a tinted tile with the title's initials rather than a blank space.
Shelf view in the Catalogue — a grid of covers, switchable with the existing list.
Database size is now shown on Settings → Backups, with a projection of what the 14-backup rotation will use.
Bulk copy labels. The Catalogue's Labels button covers every copy currently listed — the whole catalogue, or just what a search or category filter has narrowed it to — instead of one title at a time. Both the printed sheet and a new Download ZIP (one SVG per copy, for laying labels out in other software) work from that same selection, and the existing scannability check still refuses a label size too small to read. Withdrawn copies are skipped; copies currently on loan are included, since they still need a label on the shelf they return to.
Notes
- Covers are stored inside the database, not in a separate folder. That is deliberate: a backup copies the database file and nothing else, so covers kept alongside it would never be backed up and would disappear on a restore. The trade-off is file size — roughly 120 KB per cover, and each of the 14 rotating backups is a full copy. The new database-size readout is there to keep that visible.
- Existing 1.5.0 databases gain the new columns automatically on first launch; nothing else changes.
- Demo data now includes cover art. Choosing "Explore with demo data" generates a typographic cover for every seeded book — colour-coded by category, with the title and author set on it — so the Shelf view and the desk have something to show. They are deliberately typographic rather than imitation photographs: a demo should look like a demo, and inventing artwork for real books by real authors would be misleading. Generating them takes a moment and the setup screen reports progress.
1.5.0
LibTrack now tracks what is in the library, not just who walks into it.
Added
Catalogue. Books are recorded as a title plus its individual copies. Each copy gets its own barcode label, printable in bulk at an exact millimetre size — with the same scannability check the ID cards use, so a label too small to scan is refused before you print 300 of them.
Circulation desk. Scan a borrower's card, then scan books onto them. Returns need only the book — the copy already knows who has it. Scanning is routed automatically: the app can tell a copy label from a publisher ISBN from an ID card, so staff never pick the right box first.
Borrowers can be students, teachers, or outside borrowers (a parent, an alumnus). Outside borrowers are saved records, unlike walk-in guest visits — you cannot chase an unreturned book without one.
Due dates, renewals and overdue tracking, with loan length and per-borrower item limits configurable in Settings → Lending rules. Teachers get their own, longer loan period.
Optional fines. Off by default. When enabled, a late return records a fine at the rate in force at that moment. ⚠️ Changing the rate later never alters a fine already recorded — a debt keeps the amount it was charged at.
ISBN lookup. Type or scan an ISBN and press Look up to fill the form from Open Library. Entirely optional: with no internet it says so and you type the details in. Cataloguing a second copy of a title you already hold never needs the network at all.
Loans screen listing everything out, overdues first, plus the fines ledger with payments and waivers, and an Excel export.
Dashboard shows On loan / Overdue / Titles — but only once something has been catalogued, so attendance-only schools see no empty figures.
Demo data now includes a library. "Explore with demo data" seeds 14 titles, ~35 barcoded copies, borrowers, loan history, books currently out, three overdue items and a small fines ledger — so the circulation screens can be tried without cataloguing anything first. Fines are switched on in demo mode only; a real install still starts with them off.
Fixed
- "Reset to clean" now really does. It never deleted walk-in guest visits (since 1.3.0), and would have left the whole catalogue, loan history and fine settings behind — so a "clean" install could come back still holding books and charging the demo's fine rate.
Notes
- Existing databases upgrade automatically; the new tables are added on first launch and attendance data is untouched.
- Deleting a catalogued title that has ever been borrowed is refused — mark its copies withdrawn instead, so the borrowing record and any fines survive.
- Cataloguing and lending rules are admin-only. Running the desk (check-out, check-in, renew) is open to any signed-in staff member, since locking that to admins would stop the library working.
1.4.0
Added
- Spoken name announcements. Settings → Sound & announcements offers three modes: Off, Chime, or
Announce names, which says the surname and direction aloud — "Bornea, in." / "Bornea, out." — so
the desk can stay heads-down and still catch a wrong card.
- Works fully offline, using the voices already installed in Windows. No internet, no extra software.
- ⚠️ Listen before rolling this out. The English voices bundled with Windows mispronounce many Filipino surnames. If Filipino voices are installed (Windows Settings → Time & Language → Speech) the app prefers them automatically; a Test button and a voice picker are provided for this reason.
- Guests are not announced by name — a guest name is typed freehand and may be a title or an organisation. They get the chime instead.
- Default is off, and existing machines that had the sound toggle on come back as Chime — nothing starts speaking on its own after updating. Note that when enabled, every visitor's surname is audible to the room; that is the school's call to make.
Changed
- Timing out now makes a sound. It was previously silent. Exits get a distinct falling chime against the rising one for entries, so the two are tellable apart without looking.
- The "Scan confirmation sound" on/off switch has been replaced by the three-way control above.
1.3.0
Added
- Guest visitors. Walk-in → "Sign in a guest" records someone who is not an enrolled student — a
parent, an alumnus, staff from another school — with a name and a purpose of visit. Previously these
people could not be logged at all without inventing a fake student record.
- Guests appear in the same walk-in log as students, marked with a Guest badge, and time out the same way. The "Inside now" count includes them.
- They are kept entirely out of the student roster: section enrolment counts, the per-grade report and the ID card export are unaffected, by design.
- Visits are stored permanently and file into that day's session, so they stay visible in history. There is no saved guest list — a returning visitor is simply a new entry, and their name is typed each time.
- Reports export a separate Guests sheet (added only when there are guest visits in the period).
- Signing in a guest does not require the staff password that manual student entry does — a guest record grants no student attendance credit, so gating it would block the desk without preventing misuse. Deleting a guest visit is admin-only.
1.2.1
Fixed
- Confirmation prompts now match the app. Removing a teacher, student or section — or discarding an
empty session — opened a plain grey Windows dialog titled
libtrack, drawn by the browser engine and unable to follow the app's theme or fonts. These now use the same dialog style as the rest of LibTrack, with the delete button in red and labelled with the action ("Remove teacher") rather than a bare "OK". Cancel is focused by default, so pressing Enter by reflex no longer deletes anything.
1.2.0
Added
- Student ID cards are resizable. The export dialog now takes a card size — ID card (CR80), two label sizes, or a custom width and height in millimetres — and prints at that exact physical size.
- Bulk download of ID cards. "Download ZIP" produces one archive with a barcode SVG per student, for laying out IDs in another program. SVG so it stays sharp at any size.
- Barcode scannability check. A barcode stops reading below a minimum printed bar width. The export dialog now calculates that width from your longest student ID and blocks the export if it would print too small, telling you the width that would work.
- Duplicate IDs are caught while you type. Entering a student ID, teacher username or section code that is already taken now flags the field immediately and names who holds it.
Fixed
- Database errors no longer reach the screen. Adding a duplicate student showed
UNIQUE constraint failed: students.student_id; it now says which student already has that ID. All database errors are translated before display. - Actions that failed silently now report it — deleting a student or section, timing a visitor out, and exporting a report gave no feedback at all when they failed.
- Error messages no longer look like successes. Error toasts showed the same green check as a successful save; they now show a red alert icon and stay on screen longer.
- "Lock now" on Walk-in no longer claims success when locking failed — a security control that reported the wrong state.
- Errors from one dialog could appear inside an unrelated one on the Sections screen, and stale messages persisted when a dialog was reopened.
- Removed the unused
qrcodedependency (19 packages) left over from the barcode switch.
1.1.0
Added
- Mark a whole section present or absent at once, then adjust individuals. Only fills in students with no record yet, so pressing it twice never undoes a manual change.
- Walk-in "Add manually" moved to the top of the page as a full-width search bar, with Enter to add the top match.
- Barcodes replace QR codes on student ID cards (CODE_128), with USB barcode scanner support — a scanner types the ID and presses Enter, which is far more reliable than a webcam for barcodes. The camera still works as a fallback, and previously printed QR cards still scan.
- Audit trail. Every check-in records who was signed in and whether it came from a scan, manual entry or bulk marking. Shown in session detail and included in the Excel export.
Changed
- ⚠️ Manual walk-in entry now requires a staff password, which unlocks it for 10 minutes (configurable in Settings → Walk-in security). This prevents a student at an unattended kiosk from timing in their friends. Scanning a card is never blocked. Expect questions from staff who used the search box daily — this is the most visible change in this release.
- Walk-in resets daily. It previously reused one session forever, which meant anyone who forgot to time out was permanently locked out, and the "Inside now" count only ever climbed. Each day now gets its own session, filed into history automatically.
Fixed
- The window could not be dragged after the frameless title bar was introduced.
- Text fields could not be typed into on the login and password screens.
- Cards could collide with each other on a page; page-level spacing is now automatic.
- Dates used UTC, which would have rolled the "day" over at 8am local time in UTC+8.
1.0.0
Initial release — class sessions, walk-in attendance, QR student IDs, sections, teachers, reports with Excel export, backups, and role-based access.