${escapeHtml(title)}
${escapeHtml(copy)}
diff --git a/README.md b/README.md
index 5b6c362..22e54d9 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,8 @@ A small, shared tracker for the first two years of Stardew Valley. It combines:
- birthdays, festivals, seasonal events, and weekly reminders; and
- one server-side checklist that stays in sync across phones and computers.
+Every actionable card and checklist row uses the same shared checkbox behavior. Date and season selection remain local to each device, so two players can look at different days without moving each other's calendar.
+
The app is deliberately dependency-free at runtime: Python's standard library serves the site and stores progress in SQLite. Nginx/Authelia are expected to provide the public access boundary.
## Run locally
diff --git a/app.py b/app.py
index eac1bfb..5f9095d 100644
--- a/app.py
+++ b/app.py
@@ -9,6 +9,7 @@ import logging
import mimetypes
import re
import sqlite3
+from contextlib import closing
from datetime import datetime, timezone
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
@@ -38,7 +39,7 @@ class Store:
return connection
def initialize(self) -> None:
- with self.connect() as db:
+ with closing(self.connect()) as db, db:
db.executescript(
"""
CREATE TABLE IF NOT EXISTS checks (
@@ -56,7 +57,7 @@ class Store:
)
def snapshot(self) -> dict:
- with self.connect() as db:
+ with closing(self.connect()) as db, db:
revision = int(
db.execute("SELECT value FROM metadata WHERE key = 'revision'").fetchone()[0]
)
@@ -75,7 +76,7 @@ class Store:
def set_check(self, item_id: str, checked: bool, user: str) -> dict:
changed_at = utc_now()
- with self.connect() as db:
+ with closing(self.connect()) as db, db:
db.execute("BEGIN IMMEDIATE")
db.execute(
"""
@@ -206,7 +207,10 @@ class Handler(BaseHTTPRequestHandler):
}
self.send_header("Content-Type", f"{content_type}; charset=utf-8" if textual else content_type)
self.send_header("Content-Length", str(len(body)))
- self.send_header("Cache-Control", "no-cache" if target.name == "index.html" else "public, max-age=3600")
+ # Source filenames are intentionally stable. Always revalidate them so
+ # two household clients cannot run different interaction code for an
+ # hour after a deployment.
+ self.send_header("Cache-Control", "no-cache" if textual else "public, max-age=3600")
self.end_headers()
self.wfile.write(body)
diff --git a/flake.nix b/flake.nix
index b304734..6ace621 100644
--- a/flake.nix
+++ b/flake.nix
@@ -30,6 +30,8 @@
python3 -m py_compile app.py
node --check static/app.js
node --check static/data.js
+ node --check static/sync.js
+ node tests/test_sync.js
touch $out
'';
});
diff --git a/package.nix b/package.nix
index 9f9fa44..23f453a 100644
--- a/package.nix
+++ b/package.nix
@@ -2,7 +2,7 @@
stdenvNoCC.mkDerivation {
pname = "stardew-tracker";
- version = "1.0.0";
+ version = "1.1.0";
src = ./.;
nativeBuildInputs = [ makeWrapper ];
diff --git a/static/app.js b/static/app.js
index 7c64b42..6a3ac8c 100644
--- a/static/app.js
+++ b/static/app.js
@@ -1,5 +1,6 @@
(() => {
const data = window.VALLEY_DATA;
+ const sync = window.ValleySync;
const weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
const seasonNames = { spring: "Spring", summer: "Summer", fall: "Fall", winter: "Winter" };
const state = {
@@ -89,22 +90,31 @@
return data.crops.filter(crop => crop.season === state.season && crop.last === state.day && (!crop.year || crop.year === state.year));
}
- function card(kind, label, title, copy) {
- return ` ${escapeHtml(copy)}${escapeHtml(title)}
${escapeHtml(copy)}
`; + return itemId + ? `` + : `At a glance
Tick a card when you’ve handled it.