sky form TZ: render-readonly + drop #id_nf_tz_hint; placeholder absorbs the auto-detected hint copy — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

A user-typed TZ override fed through schedulePreview's `if (tz) params.set('tz', tz)` path made PySwiss compute the chart against a TZ that didn't match the lat/lon, so a partial edit (e.g. "America/New_Yo|") returned HTTP 400. Mirror the lat/lon convention: tz field gets readonly + tabindex:-1 across all three sky contexts (Dashsky sky.html, in-room PICK SKY _sky_overlay.html, My Sky applet _applet-my-sky.html). Auto-population still works because the JS writes via .value rather than via user input. The <small id="id_nf_tz_hint"> "Auto-detected from coordinates." line is removed; that copy now lives on the <input>'s placeholder so an empty TZ field self-explains. JS purges every tzHint reference (const declaration + 4 .textContent writes per file × 3 files).

SkyViewTest.test_tz_input_is_readonly_and_carries_auto_detect_placeholder pins the rendered Dashsky markup: id_nf_tz carries `readonly`, the placeholder is "auto-detected from coordinates", and `id="id_nf_tz_hint"` no longer appears anywhere. Existing MySkyTimezoneRefreshTest still passes — it asserts the field auto-fills via JS, which still works on a readonly input.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-08 15:27:09 -04:00
parent 8a8d1536b1
commit 1111df8465
4 changed files with 24 additions and 21 deletions

View File

@@ -37,6 +37,22 @@ class SkyViewTest(TestCase):
self.assertContains(response, reverse("sky_preview"))
self.assertContains(response, reverse("sky_save"))
def test_tz_input_is_readonly_and_carries_auto_detect_placeholder(self):
"""Manual TZ edits throw the schedulePreview / PySwiss fetch off (the
backend gets a stale TZ for the new lat/lon), so the field is render-
readonly like lat/lon — auto-fills from preview, never from a typed
override. The old <small id="id_nf_tz_hint"> is gone; its copy lives
in the placeholder so an empty field is self-explanatory."""
response = self.client.get(reverse("sky"))
self.assertContains(response, 'id="id_nf_tz"')
# readonly + tabindex:-1 mirrors the lat/lon pattern.
self.assertRegex(
response.content.decode(),
r'id="id_nf_tz"[^>]*\breadonly\b',
)
self.assertContains(response, 'placeholder="auto-detected from coordinates"')
self.assertNotContains(response, 'id="id_nf_tz_hint"')
class SkyPreviewTest(TestCase):
def setUp(self):

View File

@@ -54,8 +54,8 @@
<div class="sky-field">
<label for="id_nf_tz">Timezone</label>
<input id="id_nf_tz" name="tz" type="text"
placeholder="auto-detected from location">
<small id="id_nf_tz_hint"></small>
placeholder="auto-detected from coordinates"
readonly tabindex="-1">
</div>
</form>
@@ -145,7 +145,6 @@
const latInput = document.getElementById('id_nf_lat');
const lonInput = document.getElementById('id_nf_lon');
const tzInput = document.getElementById('id_nf_tz');
const tzHint = document.getElementById('id_nf_tz_hint');
const suggestions = document.getElementById('id_nf_suggestions');
const PREVIEW_URL = section.dataset.previewUrl;
@@ -185,7 +184,7 @@
if (d.place) placeInput.value = d.place;
if (d.lat) latInput.value = d.lat;
if (d.lon) lonInput.value = d.lon;
if (d.tz) { tzInput.value = d.tz; tzHint.textContent = 'Auto-detected from coordinates.'; }
if (d.tz) { tzInput.value = d.tz; }
if (_formReady()) schedulePreview();
}
@@ -250,7 +249,6 @@
latInput.value = parseFloat(place.lat).toFixed(4);
lonInput.value = parseFloat(place.lon).toFixed(4);
tzInput.value = '';
tzHint.textContent = '';
hideSuggestions();
_saveForm();
schedulePreview();
@@ -269,7 +267,6 @@
latInput.value = pos.coords.latitude.toFixed(4);
lonInput.value = pos.coords.longitude.toFixed(4);
tzInput.value = '';
tzHint.textContent = '';
fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latInput.value}&lon=${lonInput.value}`,
{ headers: { 'User-Agent': USER_AGENT } }
@@ -330,7 +327,6 @@
_lastChartData = d;
if (!tzInput.value && d.timezone) {
tzInput.value = d.timezone;
tzHint.textContent = 'Auto-detected from coordinates.';
}
setStatus('');
confirmBtn.disabled = false;

View File

@@ -64,9 +64,9 @@
<div class="sky-field">
<label for="id_nf_tz">Timezone</label>
<input id="id_nf_tz" name="tz" type="text"
placeholder="auto-detected from location"
placeholder="auto-detected from coordinates"
readonly tabindex="-1"
{% if saved_birth_tz %}value="{{ saved_birth_tz }}"{% endif %}>
<small id="id_nf_tz_hint"></small>
</div>
</form>
@@ -114,7 +114,6 @@
const latInput = document.getElementById('id_nf_lat');
const lonInput = document.getElementById('id_nf_lon');
const tzInput = document.getElementById('id_nf_tz');
const tzHint = document.getElementById('id_nf_tz_hint');
const suggestions = document.getElementById('id_nf_suggestions');
const PREVIEW_URL = overlay.dataset.previewUrl;
@@ -191,7 +190,6 @@
latInput.value = parseFloat(place.lat).toFixed(4);
lonInput.value = parseFloat(place.lon).toFixed(4);
tzInput.value = '';
tzHint.textContent = '';
hideSuggestions();
schedulePreview();
}
@@ -209,7 +207,6 @@
latInput.value = pos.coords.latitude.toFixed(4);
lonInput.value = pos.coords.longitude.toFixed(4);
tzInput.value = '';
tzHint.textContent = '';
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latInput.value}&lon=${lonInput.value}`, {
headers: { 'User-Agent': USER_AGENT },
})
@@ -267,7 +264,6 @@
_lastChartData = data;
if (!tzInput.value && data.timezone) {
tzInput.value = data.timezone;
tzHint.textContent = 'Auto-detected from coordinates.';
}
setStatus('');
confirmBtn.disabled = false;

View File

@@ -71,8 +71,8 @@
<div class="sky-field">
<label for="id_nf_tz">Timezone</label>
<input id="id_nf_tz" name="tz" type="text"
placeholder="auto-detected from location">
<small id="id_nf_tz_hint"></small>
placeholder="auto-detected from coordinates"
readonly tabindex="-1">
</div>
</form>
@@ -122,7 +122,6 @@
const latInput = document.getElementById('id_nf_lat');
const lonInput = document.getElementById('id_nf_lon');
const tzInput = document.getElementById('id_nf_tz');
const tzHint = document.getElementById('id_nf_tz_hint');
const suggestions = document.getElementById('id_nf_suggestions');
const PREVIEW_URL = overlay.dataset.previewUrl;
@@ -166,7 +165,7 @@
if (data.place) placeInput.value = data.place;
if (data.lat) latInput.value = data.lat;
if (data.lon) lonInput.value = data.lon;
if (data.tz) { tzInput.value = data.tz; tzHint.textContent = 'Auto-detected from coordinates.'; }
if (data.tz) { tzInput.value = data.tz; }
}
// ── Open / Close ──────────────────────────────────────────────────────────
@@ -251,7 +250,6 @@
latInput.value = parseFloat(place.lat).toFixed(4);
lonInput.value = parseFloat(place.lon).toFixed(4);
tzInput.value = '';
tzHint.textContent = '';
hideSuggestions();
_saveForm();
schedulePreview();
@@ -270,7 +268,6 @@
latInput.value = pos.coords.latitude.toFixed(4);
lonInput.value = pos.coords.longitude.toFixed(4);
tzInput.value = '';
tzHint.textContent = '';
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latInput.value}&lon=${lonInput.value}`, {
headers: { 'User-Agent': USER_AGENT },
})
@@ -335,7 +332,6 @@
// Back-fill timezone field from proxy response (first render)
if (!tzInput.value && data.timezone) {
tzInput.value = data.timezone;
tzHint.textContent = 'Auto-detected from coordinates.';
}
setStatus('');
@@ -429,7 +425,6 @@
latInput.value = '';
lonInput.value = '';
tzInput.value = '';
tzHint.textContent = '';
_lastChartData = null;
confirmBtn.disabled = true;
setStatus('');