fix: remove all non-ASCII characters that cause Windows encoding issues

- PIN input: removed literal bullet placeholder (was the reported gibberish)
- Search placeholders: ... -> ... (ASCII)
- Custom... period option in statement: uses … entity
- app.js/common.js/style.css/bar.js/cashier.js: en/em dashes in
  comments -> ASCII hyphens; Unicode minus sign U+2212 -> plain hyphen
- main.py: arrows and dashes in comments -> ASCII
- bar.html/cashier.html: standalone page titles and placeholders fixed

Intentional: pound sign in currency defaults stays as UTF-8 (served
with Content-Type: text/html; charset=utf-8 and correct HTTP headers).

https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
This commit is contained in:
Claude 2026-05-31 03:48:14 +00:00
parent 59b49a3d3a
commit ca344e3762
No known key found for this signature in database
9 changed files with 31 additions and 31 deletions

24
main.py
View file

@ -1,5 +1,5 @@
""" """
ClubLedger Store Credit Web App ClubLedger - Store Credit Web App
Hard defaults live in CONFIG below; everything is overridable via the Admin UI. Hard defaults live in CONFIG below; everything is overridable via the Admin UI.
""" """
@ -96,7 +96,7 @@ DB_PATH = "clubledger.db"
STAFF_FILE = Path(__file__).parent / "staff.json" STAFF_FILE = Path(__file__).parent / "staff.json"
static_dir = Path(__file__).parent / "static" static_dir = Path(__file__).parent / "static"
# In-memory sessions: token {user_id, name, role, expires} # In-memory sessions: token -> {user_id, name, role, expires}
_sessions: dict = {} _sessions: dict = {}
# Cached settings merged from CONFIG + DB app_settings # Cached settings merged from CONFIG + DB app_settings
@ -232,7 +232,7 @@ def migrate_db():
conn.execute("DROP TABLE _ledger_entries_old") conn.execute("DROP TABLE _ledger_entries_old")
conn.execute("CREATE INDEX IF NOT EXISTS idx_ledger_member ON ledger_entries(member_id)") conn.execute("CREATE INDEX IF NOT EXISTS idx_ledger_member ON ledger_entries(member_id)")
# --- app_settings: rename allow_negative_balance overdraft_policy --- # --- app_settings: rename allow_negative_balance -> overdraft_policy ---
row = conn.execute( row = conn.execute(
"SELECT value FROM app_settings WHERE key='allow_negative_balance'" "SELECT value FROM app_settings WHERE key='allow_negative_balance'"
).fetchone() ).fetchone()
@ -260,8 +260,8 @@ def seed_admin():
("Administrator", "admin", pw, "admin") ("Administrator", "admin", pw, "admin")
) )
print("=" * 60) print("=" * 60)
print(" Default admin created username: admin password: admin") print(" Default admin created -> username: admin password: admin")
print(" Change this immediately in the Admin Staff Accounts area.") print(" Change this immediately in the Admin -> Staff Accounts area.")
print("=" * 60) print("=" * 60)
def refresh_settings(): def refresh_settings():
@ -782,7 +782,7 @@ def _period_bounds(period: str, s: dict,
start = _local(fd); end = _local(td) + timedelta(days=1) start = _local(fd); end = _local(td) + timedelta(days=1)
except ValueError: except ValueError:
start = _local(today); end = start + timedelta(days=1) start = _local(today); end = start + timedelta(days=1)
else: # fallback today else: # fallback -> today
start = _local(today); end = start + timedelta(days=1) start = _local(today); end = start + timedelta(days=1)
fmt = "%Y-%m-%d %H:%M:%S" fmt = "%Y-%m-%d %H:%M:%S"
@ -869,7 +869,7 @@ def transactions(member_id: int, limit: int = 50, offset: int = 0,
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Print views (no auth opened as new-tab popups) # Print views (no auth - opened as new-tab popups)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _print_size_script(s: dict): def _print_size_script(s: dict):
@ -996,7 +996,7 @@ _STMT_PERIODS = [
("last_quarter", "Last quarter"), ("last_quarter", "Last quarter"),
("year", "This year"), ("year", "This year"),
("last_year", "Last year"), ("last_year", "Last year"),
("custom", "Custom"), ("custom", "Custom…"),
] ]
def _stmt_period_selector(period: str, from_date: str, to_date: str) -> str: def _stmt_period_selector(period: str, from_date: str, to_date: str) -> str:
@ -1287,7 +1287,7 @@ def remove_staff(name: str, user: dict = Depends(current_user)):
return {"staff": staff} return {"staff": staff}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Admin staff accounts # Admin - staff accounts
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@app.get("/admin/staff-accounts") @app.get("/admin/staff-accounts")
@ -1354,7 +1354,7 @@ def delete_staff_account(account_id: int, user: dict = Depends(admin_user)):
return {"ok": True} return {"ok": True}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Admin app settings # Admin - app settings
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@app.get("/admin/settings") @app.get("/admin/settings")
@ -1379,7 +1379,7 @@ def update_admin_settings(body: AppSettingsUpdate, user: dict = Depends(admin_us
return _settings return _settings
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Admin logo upload # Admin - logo upload
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@app.post("/admin/logo") @app.post("/admin/logo")
@ -1400,7 +1400,7 @@ async def upload_logo(file: UploadFile = File(...), user: dict = Depends(admin_u
return {"url": url} return {"url": url}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Config (public loaded by frontend before login screen shows) # Config (public - loaded by frontend before login screen shows)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@app.get("/config") @app.get("/config")

View file

@ -1,4 +1,4 @@
/* ClubLedger main SPA */ /* ClubLedger - main SPA */
let currentUser = null; let currentUser = null;
let cashierMember = null; let cashierMember = null;
@ -7,7 +7,7 @@ let editMemberId = null;
let editAccountId = null; let editAccountId = null;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Boot check session, then either show login or start the app // Boot - check session, then either show login or start the app
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
(async function boot() { (async function boot() {
// Load config first so the login page shows the club name // Load config first so the login page shows the club name
@ -80,7 +80,7 @@ function populateTimezoneList() {
list.innerHTML = Intl.supportedValuesOf('timeZone') list.innerHTML = Intl.supportedValuesOf('timeZone')
.map(z => `<option value="${z}">`) .map(z => `<option value="${z}">`)
.join(''); .join('');
} catch (e) { /* older browser plain text input still works */ } } catch (e) { /* older browser -- plain text input still works */ }
} }
async function startApp() { async function startApp() {
@ -334,9 +334,9 @@ async function loadCashierStats() {
setCol('statsWithdrawals', 'statsWithdrawalsCount', d.withdrawals, 'stats-negative'); setCol('statsWithdrawals', 'statsWithdrawalsCount', d.withdrawals, 'stats-negative');
setCol('statsCharges', 'statsChargesCount', d.charges, 'stats-negative'); setCol('statsCharges', 'statsChargesCount', d.charges, 'stats-negative');
const netEl = document.getElementById('statsNet'); const netEl = document.getElementById('statsNet');
netEl.textContent = (d.net.negative ? '' : '+') + d.net.display; netEl.textContent = (d.net.negative ? '-' : '+') + d.net.display;
netEl.className = 'stats-col-value ' + (d.net.negative ? 'stats-negative' : 'stats-positive'); netEl.className = 'stats-col-value ' + (d.net.negative ? 'stats-negative' : 'stats-positive');
} catch (e) { /* silently ignore widgets are non-critical */ } } catch (e) { /* silently ignore -- widgets are non-critical */ }
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bar ClubLedger</title> <title>Bar - ClubLedger</title>
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
</head> </head>
<body> <body>
@ -19,7 +19,7 @@
<div class="panel"> <div class="panel">
<h2>Charge Account</h2> <h2>Charge Account</h2>
<div class="search-row"> <div class="search-row">
<input type="text" id="barSearch" placeholder="Search member"> <input type="text" id="barSearch" placeholder="Search member...">
<button class="btn" onclick="barSearchMembers()">Search</button> <button class="btn" onclick="barSearchMembers()">Search</button>
</div> </div>
<div id="barMemberList" class="member-pick-list"></div> <div id="barMemberList" class="member-pick-list"></div>

View file

@ -1,4 +1,4 @@
/* ClubLedger bar page */ /* ClubLedger - bar page */
let barMember = null; let barMember = null;

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cashier ClubLedger</title> <title>Cashier - ClubLedger</title>
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
</head> </head>
<body> <body>
@ -41,7 +41,7 @@
<div class="panel"> <div class="panel">
<h2>Top Up Account</h2> <h2>Top Up Account</h2>
<div class="search-row"> <div class="search-row">
<input type="text" id="cashierSearch" placeholder="Search member"> <input type="text" id="cashierSearch" placeholder="Search member...">
<button class="btn" onclick="cashierSearchMembers()">Search</button> <button class="btn" onclick="cashierSearchMembers()">Search</button>
</div> </div>
<div id="cashierMemberList" class="member-pick-list"></div> <div id="cashierMemberList" class="member-pick-list"></div>
@ -70,7 +70,7 @@
<div class="panel"> <div class="panel">
<h2>Members</h2> <h2>Members</h2>
<div class="search-row"> <div class="search-row">
<input type="text" id="memberSearch" placeholder="Search name or number"> <input type="text" id="memberSearch" placeholder="Search name or number...">
<button class="btn" onclick="searchMembers()">Search</button> <button class="btn" onclick="searchMembers()">Search</button>
</div> </div>
<table id="memberTable" class="data-table"> <table id="memberTable" class="data-table">

View file

@ -1,4 +1,4 @@
/* ClubLedger cashier page */ /* ClubLedger - cashier page */
let cashierMember = null; let cashierMember = null;

View file

@ -1,4 +1,4 @@
/* ClubLedger shared helpers */ /* ClubLedger - shared helpers */
let cfg = { currency_unit: 'pence', currency_symbol: '£', currency_divisor: 100, club_name: 'ClubLedger' }; let cfg = { currency_unit: 'pence', currency_symbol: '£', currency_divisor: 100, club_name: 'ClubLedger' };

View file

@ -81,7 +81,7 @@
<div class="panel"> <div class="panel">
<h2>Members</h2> <h2>Members</h2>
<div class="search-row"> <div class="search-row">
<input type="text" id="memberSearch" placeholder="Search name or number"> <input type="text" id="memberSearch" placeholder="Search name or number...">
<button class="btn" onclick="searchMembers()">Search</button> <button class="btn" onclick="searchMembers()">Search</button>
</div> </div>
<table id="memberTable" class="data-table"> <table id="memberTable" class="data-table">
@ -154,7 +154,7 @@
<div class="panel"> <div class="panel">
<h2>Top Up Account</h2> <h2>Top Up Account</h2>
<div class="search-row"> <div class="search-row">
<input type="text" id="cashierSearch" placeholder="Search member"> <input type="text" id="cashierSearch" placeholder="Search member...">
<button class="btn" onclick="cashierSearchMembers()">Search</button> <button class="btn" onclick="cashierSearchMembers()">Search</button>
</div> </div>
<div id="cashierMemberList" class="member-pick-list"></div> <div id="cashierMemberList" class="member-pick-list"></div>
@ -225,7 +225,7 @@
<div class="panel"> <div class="panel">
<h2>Charge Account</h2> <h2>Charge Account</h2>
<div class="search-row"> <div class="search-row">
<input type="text" id="barSearch" placeholder="Search member"> <input type="text" id="barSearch" placeholder="Search member...">
<button class="btn" onclick="barSearchMembers()">Search</button> <button class="btn" onclick="barSearchMembers()">Search</button>
</div> </div>
<div id="barMemberList" class="member-pick-list"></div> <div id="barMemberList" class="member-pick-list"></div>
@ -253,7 +253,7 @@
<div class="pin-charge-amount" id="pinAmount"></div> <div class="pin-charge-amount" id="pinAmount"></div>
<div class="pin-member-name" id="pinMember"></div> <div class="pin-member-name" id="pinMember"></div>
<div class="pin-instruction">Please enter your PIN</div> <div class="pin-instruction">Please enter your PIN</div>
<input type="password" id="barPin" class="pin-input" placeholder="••••" <input type="password" id="barPin" class="pin-input"
maxlength="20" autocomplete="off" inputmode="numeric" maxlength="20" autocomplete="off" inputmode="numeric"
onkeydown="if(event.key==='Enter') confirmCharge()"> onkeydown="if(event.key==='Enter') confirmCharge()">
<div id="pinMsg" class="msg"></div> <div id="pinMsg" class="msg"></div>

View file

@ -1,4 +1,4 @@
/* ClubLedger GitHub-style theme */ /* ClubLedger - GitHub-style theme */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root { :root {
@ -43,7 +43,7 @@ nav {
z-index: 100; z-index: 100;
} }
/* On desktop the wrapper is invisible buttons behave as direct nav children */ /* On desktop the wrapper is invisible -- buttons behave as direct nav children */
.nav-tabs { display: contents; } .nav-tabs { display: contents; }
.brand { .brand {