PunctuOwlity stores all user and event data locally on the device or in the browser — no server or cloud backend is involved. The web application uses the browser’s Web Storage APIs, while the Android application uses a SQLite database managed through a customDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/PunctuOwlity/llms.txt
Use this file to discover all available pages before exploring further.
SQLiteOpenHelper subclass. Both approaches keep the data entirely within the user’s environment, which makes the project suitable for static hosting and offline use but also means that data does not sync across devices or sessions.
Web Storage
The web app uses two browser storage mechanisms:localStorage for data that must survive between sessions, and sessionStorage for flags that should reset when the browser tab is closed.
localStorage Keys
| Key | Type | Purpose |
|---|---|---|
punctuowlity-events | JSON array of event objects | All saved events; falls back to seed events if absent or unparseable |
punctuowlity-users | JSON array of user objects | All registered user accounts |
punctuowlity-sms | 'allowed' | 'denied' | The user’s SMS/notification preference; absence means the preference has not been set yet |
sessionStorage Keys
| Key | Value | Purpose |
|---|---|---|
punctuowlity-authenticated | 'true' | Set after a successful login; cleared when the tab closes. Pages with data-page="main" redirect to index.html if this key is absent. |
punctuowlity-notified-{id}-{date} | 'true' | Deduplication flag for browser notifications. One key per event per calendar date; prevents duplicate alerts within the same session. |
Event Object Schema
Each entry in thepunctuowlity-events array conforms to the following shape. Fields are written by normalizeEvent before the record is saved or rendered.
Unique identifier. Set to a seed keyword (e.g.
'birthday') for built-in events; set to String(Date.now()) for user-created events.Three-letter weekday abbreviation in uppercase, e.g.
'SAT', 'MON'. Derived from fullDate by normalizeEvent.Two-digit day-of-month string, e.g.
'05', '14'. Derived from fullDate by normalizeEvent.ISO 8601 date string in
YYYY-MM-DD format, e.g. '2025-04-05'. Used as the canonical date for parsing and notification matching.24-hour time string in
HH:MM format, e.g. '13:30'. Empty string for all-day events.Display-formatted time in compact 12-hour format, e.g.
'01:30PM'. Set to 'ALL DAY' when rawTime is empty.The event title as entered by the user, e.g.
'Dentist', "Jessica's Wedding".One of
'all', 'birthday', 'appointment', 'trip', or 'general'. Auto-detected from the title if not explicitly set.Whether the user enabled a reminder for this event. Controls which alarm icon is displayed on the card and whether a browser notification is fired.
User Object Schema
Each entry in thepunctuowlity-users array has the following fields. The web version stores more profile fields than the Android version because the sign-up form collects them.
Unique identifier; set to
String(Date.now()) at registration time.User’s first name.
User’s last name.
Email address (lowercased). Accepted as an alternative login credential alongside
username.Optional phone number, collected for potential SMS use but not transmitted anywhere in the static app.
Chosen username (lowercased for comparison). Must be unique across all stored users.
Password as entered. See the warning below regarding plain-text storage.
Data Normalization
ThenormalizeEvent function in app.js is called on every event before it is rendered or saved. It handles two categories of stored data that need conversion before display: raw date strings and raw time strings.
Date Parsing
normalizeEvent accepts two date input formats and converts both to YYYY-MM-DD for the canonical fullDate field:
YYYY-MM-DD— already in the correct format; parsed directly vianew Date($T00:00:00)to avoid timezone drift.MM/DD/YYYY— the format used by the AndroidEventmodel. Split on/, reassembled asYYYY-MM-DD, then parsed.
day) and two-digit day-of-month (date) are derived from the Date object using toLocaleDateString('en-US', { weekday: 'short' }) and getDate() respectively. If parsing fails or produces an invalid date, the function falls back to whatever day and date values are already in the record, or to '---' and '00'.
Time Conversion
IfrawTime matches the pattern HH:MM (24-hour), it is converted to the compact 12-hour display format used on the event cards:
rawTime produces the string 'ALL DAY'.
Category Auto-Detection
If an event record has nocategory field set, normalizeEvent inspects the lowercased title and assigns one of the following categories automatically:
| Keyword match | Assigned category |
|---|---|
birthday | 'birthday' |
appointment, dentist, dental, doctor | 'appointment' |
trip, travel, vacation | 'trip' |
| (no match) | 'general' |
Android SQLite
The Android app stores data in a SQLite database file namedpunctuowlity.db at database version 1. The DatabaseHelper class extends SQLiteOpenHelper and defines two tables.
Schema
Table Details
users table
| Column | Type | Notes |
|---|---|---|
id | INTEGER | Auto-incrementing primary key |
username | TEXT UNIQUE | Used as the login credential; uniqueness is enforced at the database level |
password | TEXT | Plain text; see warning below |
events table
| Column | Type | Notes |
|---|---|---|
id | INTEGER | Auto-incrementing primary key |
title | TEXT | Event title string |
date | TEXT | Stored as MM/dd/yyyy string; parsed by Event.getDayOfWeek() and Event.getDateDay() at display time |
time | TEXT | Time string as entered |
onCreate and onUpgrade
DatabaseHelper.onCreate() executes both CREATE TABLE statements in sequence when the database is first created. onUpgrade() drops both tables with DROP TABLE IF EXISTS and then calls onCreate() again — all data is lost on a version upgrade.