Fixed focus lost on username input field

This commit is contained in:
2026-02-02 23:23:33 +01:00
parent 42ead722c6
commit 14c166d143
5 changed files with 20 additions and 58 deletions

View File

@@ -1,12 +1,8 @@
# Codex Agent Guide — CoopGameChooser # Agent Guide — Pick'n'Play
This repo is a tiny, purpose-built web app for a closed Discord group to: Also see the other related files: API.md, IIS.md, SPEC.md
1) submit game suggestions blindly
2) reveal all suggestions with authors
3) vote 010 blindly
4) reveal totals sorted by score
Tech constraints: ## Tech constraints:
- .NET 10 - .NET 10
- ASP.NET Core Minimal API - ASP.NET Core Minimal API
- Static HTML/CSS/JS (no Razor Pages, no Blazor, no HTMX) - Static HTML/CSS/JS (no Razor Pages, no Blazor, no HTMX)
@@ -15,53 +11,11 @@ Tech constraints:
- Single active “session” (one room) unless extended later - Single active “session” (one room) unless extended later
- Runs on IIS (Windows Server) - Runs on IIS (Windows Server)
This file tells Codex how to work in this repo. ## Working Style
Also see the other related files: API.md, IIS.md, SPEC.md
---
## Operating Principles
### Non-negotiables
- **Server-side enforcement of phase rules** (clients must not be trusted).
- **Blindness**:
- Suggest phase: player can only read/write their own suggestions.
- Vote phase: player can only read/write their own votes.
- Results phase: only aggregated totals are shown.
- **Minimal moving parts**: prefer `Program.cs` + a few small files over frameworks.
---
## Repo Layout Target
- `Program.cs` — Minimal host wiring (services, middleware, endpoint maps)
- `Endpoints/` — Minimal API route files split by area (state, suggest, vote, results, admin) + helpers
- `Contracts/` — DTOs and request records
- `Data/` — EF Core DbContext and migrations
- `Domain/` — Plain models: Player, Suggestion, Vote, AppState, Phase enum
- `wwwroot/`
- `index.html` — app shell; loads phase-specific views
- `app.js` — main client script (ES module)
- `js/` — shared frontend modules (e.g., API client)
- `styles.css` — minimal styling
Do not introduce MVC controllers, Razor Pages, Blazor, or SPA frameworks.
---
## Security Notes
- Cookie must be HttpOnly and SameSite=Strict
- Use HTTPS in production
- No client-side trust for blindness
---
## Codex Working Style
- After every iteration, do a git commit with a brief summary of the changes as a commit message.
- Implement API first, UI second - Implement API first, UI second
- Keep changes small and testable - Keep changes small and testable
- Prefer clarity over abstraction - Prefer clarity over abstraction
- Avoid introducing new dependencies unless they remove complexity. - Avoid introducing new dependencies unless they remove complexity.
- After every iteration, do a git commit with a brief summary of the changes as a commit message.
- Keep endpoint logic in `Endpoints/` and shared helpers/DTOs in their folders to avoid Program.cs bloat. - Keep endpoint logic in `Endpoints/` and shared helpers/DTOs in their folders to avoid Program.cs bloat.

View File

@@ -1,4 +1,4 @@
# CoopGameChooser — Product Spec (MVP) # Pick'n'Play — Product Spec (MVP)
## Goal ## Goal
A micro web app for a closed Discord group (48 players) to decide what co-op game to play using a phased process: A micro web app for a closed Discord group (48 players) to decide what co-op game to play using a phased process:
@@ -33,5 +33,4 @@ A micro web app for a closed Discord group (48 players) to decide what co-op
## Non-functional ## Non-functional
- Desktop + mobile usable - Desktop + mobile usable
- Simple polling acceptable
- IIS-hosted - IIS-hosted

View File

@@ -44,7 +44,9 @@ function setAuthUI(isAuthed) {
if (adminCard) adminCard.classList.add("hidden"); if (adminCard) adminCard.classList.add("hidden");
const loginUser = $("login-username"); const loginUser = $("login-username");
const cachedUser = getSavedUsername(); const cachedUser = getSavedUsername();
if (loginUser && cachedUser) loginUser.value = cachedUser; if (loginUser && cachedUser && !loginUser.dataset.userEditing && !loginUser.value) {
loginUser.value = cachedUser;
}
} }
} }
@@ -311,6 +313,13 @@ function setupHandlers() {
} }
setAuthMode(state.authMode); setAuthMode(state.authMode);
const loginUser = $("login-username");
if (loginUser) {
const markEditing = () => { loginUser.dataset.userEditing = "1"; };
["focus", "input", "keydown"].forEach(evt => loginUser.addEventListener(evt, markEditing));
loginUser.addEventListener("blur", () => { delete loginUser.dataset.userEditing; });
}
const langSelects = Array.from(document.querySelectorAll(".lang-select")); const langSelects = Array.from(document.querySelectorAll(".lang-select"));
const syncLanguageSelects = () => langSelects.forEach(sel => sel.value = getLanguage()); const syncLanguageSelects = () => langSelects.forEach(sel => sel.value = getLanguage());
syncLanguageSelects(); syncLanguageSelects();

View File

@@ -15,7 +15,7 @@
<body class="page"> <body class="page">
<section class="card hidden" id="auth-card"> <section class="card hidden" id="auth-card">
<div class="auth-logo"> <div class="auth-logo">
<img src="logo.png" alt="CoopGameChooser logo" /> <img src="logo.png" alt="Pick'n'Play logo" />
</div> </div>
<div class="stack"> <div class="stack">
<h2 id="auth-title" data-i18n="auth.loginHeading">Log in</h2> <h2 id="auth-title" data-i18n="auth.loginHeading">Log in</h2>
@@ -62,7 +62,7 @@
<section> <section>
<div class="status-bar"> <div class="status-bar">
<img src="logo.png" alt="CoopGameChooser logo" class="logo-mark"> <img src="logo.png" alt="Pick'n'Play logo" class="logo-mark">
<div class="status-left"> <div class="status-left">
<span id="welcome-text" data-i18n="auth.welcome">Welcome!</span> <span id="welcome-text" data-i18n="auth.welcome">Welcome!</span>
<a id="logout" href="#" class="link inline-link" data-i18n="auth.logout">Logout</a> <a id="logout" href="#" class="link inline-link" data-i18n="auth.logout">Logout</a>

View File

@@ -132,11 +132,11 @@ button.ghost { background: transparent; border-color: #d5c7b5; color: #2c1c0d; }
.card-grid { .card-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 12px; gap: 12px;
margin-top: 12px; margin-top: 12px;
width: 100%; width: 100%;
max-width: 900px; max-width: 1280px;
margin-inline: auto; margin-inline: auto;
} }
.results-grid { max-width: none; } .results-grid { max-width: none; }