@dennisrongo/dsh-weather 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/lib/client.js +193 -36
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# dsh-weather
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@dennisrongo/dsh-weather)
|
|
4
|
+
|
|
5
|
+
**npm:** [`@dennisrongo/dsh-weather`](https://www.npmjs.com/package/@dennisrongo/dsh-weather) ·
|
|
6
|
+
**source:** [dennisrongo/dsh-plugins](https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-weather)
|
|
7
|
+
|
|
8
|
+
Weather bar for the [DeepSeek Harness](https://github.com/deepseek-ai/dsh) web UI — current conditions, a short hourly outlook, humidity and wind, pinned across the top of the page.
|
|
4
9
|
|
|
5
10
|
- **Data:** [Open-Meteo](https://open-meteo.com) (free, no API key) via the browser.
|
|
6
|
-
- **Location:** `localStorage["dsh-weather:location"] = "Your City"` if set, else coarse IP
|
|
11
|
+
- **Location:** `localStorage["dsh-weather:location"] = "Your City"` if set, else a coarse IP-geolocation provider chain, else New York.
|
|
12
|
+
- **Placement:** centred on the space the shell has actually left it, not on the viewport. The bar measures the shell frame's content box — which already excludes `dsh-mission-control`'s docked rail — and subtracts any overlay flying a `data-dsh-overlay-claim="right"` marker, today `dsh-plan-board`'s plan panel. So it slides aside and sheds detail as the free span narrows instead of sitting underneath them.
|
|
7
13
|
- **Mount point:** additive `shell.overlay` slot — pure-consumer client plugin, empty host half.
|
|
8
14
|
- **Units:** Fahrenheit by default — click the temperature to toggle °F/°C. The choice is remembered in `localStorage["dsh-weather:unit"]`.
|
|
9
15
|
- **Refresh:** every 15 min, plus a manual ⟳ button.
|
|
@@ -29,4 +35,4 @@ pnpm test
|
|
|
29
35
|
name: '@dennisrongo/dsh-weather'
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
Restart the profile; the bar appears
|
|
38
|
+
Restart the profile; the bar appears across the top of the web UI.
|
package/lib/client.js
CHANGED
|
@@ -191,6 +191,9 @@ async function fetchWeather() {
|
|
|
191
191
|
var BAR_STYLES = `
|
|
192
192
|
.dshwx {
|
|
193
193
|
position: fixed;
|
|
194
|
+
/* Centred on the viewport only until the bar has measured the shell (see
|
|
195
|
+
useBandFit): the real centre is the middle of the span no docked overlay
|
|
196
|
+
has claimed, written inline. */
|
|
194
197
|
left: 50%;
|
|
195
198
|
transform: translateX(-50%);
|
|
196
199
|
top: 8px;
|
|
@@ -205,19 +208,36 @@ var BAR_STYLES = `
|
|
|
205
208
|
border: 1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.12));
|
|
206
209
|
background: var(--dsw-specific-sidebar-fill, #1b1b1c);
|
|
207
210
|
color: var(--dsw-alias-label-secondary, #cfd3d6);
|
|
208
|
-
font: 400
|
|
211
|
+
font: 400 13px/1.4 var(--dsw-font-family, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif);
|
|
209
212
|
font-variant-numeric: tabular-nums;
|
|
210
213
|
box-shadow: var(--dsw-shadow-lv3, 0 0 1px rgba(0,0,0,0.2), 0 8px 24px rgba(0,0,0,0.12));
|
|
211
214
|
cursor: default;
|
|
212
215
|
user-select: none;
|
|
213
216
|
white-space: nowrap;
|
|
217
|
+
/* DSH Desktop on Windows overlays a 36px window-drag strip at the top of the
|
|
218
|
+
viewport (#dsh-desktop-windows-drag-region: -webkit-app-region: drag,
|
|
219
|
+
z-index 2147483644, pointer-events: none) that the compositor resolves
|
|
220
|
+
BEFORE hit-testing. no-drag here is belt-and-braces only \u2014 the desktop
|
|
221
|
+
preload already grants every button no-drag !important and the bar was
|
|
222
|
+
still unclickable, so a covered element's no-drag does not punch a hole
|
|
223
|
+
in an overlapping drag element (the same failure dsh-mission-control
|
|
224
|
+
documented for its stage bar). The real fix is the layout rule below,
|
|
225
|
+
which drops the bar clear of the strip. In a plain browser all of this
|
|
226
|
+
is inert. */
|
|
227
|
+
-webkit-app-region: no-drag;
|
|
228
|
+
}
|
|
229
|
+
body.dsh-desktop-windows-titlebar-layout .dshwx {
|
|
230
|
+
/* Clear the desktop drag strip: 36px strip + the usual 8px gap. The body
|
|
231
|
+
class is added by DSH Desktop's preload on Windows only, so the browser
|
|
232
|
+
and non-Windows builds keep top: 8px. */
|
|
233
|
+
top: 44px;
|
|
214
234
|
}
|
|
215
235
|
body[data-ds-dark-theme] .dshwx { box-shadow: 0 0 0 1px rgba(0,0,0,0.5), 0 8px 24px rgba(0,0,0,0.5); }
|
|
216
236
|
.dshwx[hidden] { display: none; }
|
|
217
|
-
.dshwx-icon { font-size:
|
|
237
|
+
.dshwx-icon { font-size: 16px; }
|
|
218
238
|
.dshwx-temp {
|
|
219
239
|
color: var(--dsw-alias-label-primary, #f9fafb);
|
|
220
|
-
font-weight: 600; font-size:
|
|
240
|
+
font-weight: 600; font-size: 14px;
|
|
221
241
|
font-family: inherit; font-variant-numeric: tabular-nums;
|
|
222
242
|
border: 0; background: transparent; padding: 1px 4px; margin: 0 -2px;
|
|
223
243
|
border-radius: 6px; cursor: pointer; line-height: inherit;
|
|
@@ -225,57 +245,89 @@ body[data-ds-dark-theme] .dshwx { box-shadow: 0 0 0 1px rgba(0,0,0,0.5), 0 8px 2
|
|
|
225
245
|
.dshwx-temp:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.08)); }
|
|
226
246
|
.dshwx-temp:focus-visible { outline: 2px solid var(--dsw-alias-label-caption, #81858c); outline-offset: 1px; }
|
|
227
247
|
.dshwx-label { color: var(--dsw-alias-label-secondary, #cfd3d6); }
|
|
248
|
+
/* The bar is a ~200px pill in the shell's top band \u2014 a small surface, so the
|
|
249
|
+
loading state stays TEXT rather than becoming a skeleton, which would be
|
|
250
|
+
heavier than the string it replaced. It takes the dim caption tone the other
|
|
251
|
+
plugins use for the same rung; a MODIFIER rather than a change to
|
|
252
|
+
.dshwx-label, which is shared with the loaded condition text and must keep
|
|
253
|
+
its secondary weight. */
|
|
254
|
+
.dshwx-label.loading { color: var(--dsw-alias-label-tertiary, #adb2b8); }
|
|
228
255
|
.dshwx-where {
|
|
229
256
|
color: var(--dsw-alias-label-tertiary, #adb2b8);
|
|
230
257
|
max-width: 160px; overflow: hidden; text-overflow: ellipsis;
|
|
231
258
|
}
|
|
232
259
|
.dshwx-sep { width: 1px; height: 14px; background: var(--dsw-alias-border-l2, rgba(255,255,255,0.12)); flex: none; }
|
|
233
|
-
.dshwx-meta { color: var(--dsw-alias-label-caption, #81858c); font-size:
|
|
260
|
+
.dshwx-meta { color: var(--dsw-alias-label-caption, #81858c); font-size: 12px; }
|
|
234
261
|
.dshwx-hours { display: flex; gap: 8px; align-items: center; }
|
|
235
|
-
.dshwx-hour { display: flex; align-items: center; gap: 3px; color: var(--dsw-alias-label-caption, #81858c); font-size:
|
|
262
|
+
.dshwx-hour { display: flex; align-items: center; gap: 3px; color: var(--dsw-alias-label-caption, #81858c); font-size: 12px; }
|
|
236
263
|
.dshwx-hour b { color: var(--dsw-alias-label-secondary, #cfd3d6); font-weight: 500; }
|
|
237
264
|
.dshwx-refresh {
|
|
238
265
|
border: 0; background: transparent; cursor: pointer;
|
|
239
266
|
color: var(--dsw-alias-label-caption, #81858c);
|
|
240
|
-
font-size:
|
|
267
|
+
font-size: 14px; padding: 2px; border-radius: 50%;
|
|
241
268
|
display: grid; place-items: center;
|
|
242
269
|
}
|
|
243
270
|
.dshwx-refresh:hover { color: var(--dsw-alias-label-primary, #f9fafb); background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.08)); }
|
|
244
271
|
.dshwx-refresh.busy { animation: dshwx-spin 0.9s linear infinite; }
|
|
245
272
|
@keyframes dshwx-spin { to { transform: rotate(360deg); } }
|
|
246
|
-
.dshwx-error { color: var(--dsw-alias-state-error-primary, #ef4444); font-size:
|
|
273
|
+
.dshwx-error { color: var(--dsw-alias-state-error-primary, #ef4444); font-size: 12px; }
|
|
247
274
|
/* --- Responsive tiers ---------------------------------------------------
|
|
248
|
-
The bar is a single nowrap pill, so
|
|
249
|
-
|
|
250
|
-
|
|
275
|
+
The bar is a single nowrap pill, so it sheds detail rather than wrap. Each
|
|
276
|
+
tier also drops the separator that preceded the hidden group, otherwise
|
|
277
|
+
stray dividers float with nothing between them.
|
|
278
|
+
|
|
279
|
+
Keyed on the MEASURED band (data-fit), not on a viewport media query. The
|
|
280
|
+
space this bar actually gets is the shell's content box minus whatever a
|
|
281
|
+
docked overlay has claimed, so a 2400px window with a plan panel open can
|
|
282
|
+
leave the bar less room than a phone \u2014 a media query would call that "full"
|
|
283
|
+
and let the pill run under the panel. The measurement falls back to the
|
|
284
|
+
viewport when there is no shell frame, so the tiers still work standalone. */
|
|
251
285
|
|
|
252
286
|
/* Tablet: drop the hourly outlook and the humidity/wind readout. */
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
287
|
+
.dshwx[data-fit="tablet"] .dshwx-hours,
|
|
288
|
+
.dshwx[data-fit="tablet"] .dshwx-meta,
|
|
289
|
+
.dshwx[data-fit="tablet"] .dshwx-sep-hours,
|
|
290
|
+
.dshwx[data-fit="tablet"] .dshwx-sep-meta,
|
|
291
|
+
.dshwx[data-fit="phone"] .dshwx-hours,
|
|
292
|
+
.dshwx[data-fit="phone"] .dshwx-meta,
|
|
293
|
+
.dshwx[data-fit="phone"] .dshwx-sep-hours,
|
|
294
|
+
.dshwx[data-fit="phone"] .dshwx-sep-meta,
|
|
295
|
+
.dshwx[data-fit="tiny"] .dshwx-hours,
|
|
296
|
+
.dshwx[data-fit="tiny"] .dshwx-meta,
|
|
297
|
+
.dshwx[data-fit="tiny"] .dshwx-sep-hours,
|
|
298
|
+
.dshwx[data-fit="tiny"] .dshwx-sep-meta { display: none; }
|
|
257
299
|
|
|
258
300
|
/* Phone: tighten spacing, shrink the place name, and give the controls
|
|
259
301
|
touch-sized hit areas without changing the pill's visual weight. */
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
font-size: 11.5px;
|
|
266
|
-
}
|
|
267
|
-
.dshwx-where { max-width: 92px; }
|
|
268
|
-
.dshwx-icon { font-size: 14px; }
|
|
269
|
-
.dshwx-temp { font-size: 12.5px; padding: 5px 7px; margin: -4px -3px; }
|
|
270
|
-
.dshwx-refresh { padding: 7px; margin: -5px; }
|
|
302
|
+
.dshwx[data-fit="phone"],
|
|
303
|
+
.dshwx[data-fit="tiny"] {
|
|
304
|
+
gap: 7px;
|
|
305
|
+
padding: 4px 10px;
|
|
306
|
+
font-size: 12px;
|
|
271
307
|
}
|
|
308
|
+
.dshwx[data-fit="phone"] .dshwx-where,
|
|
309
|
+
.dshwx[data-fit="tiny"] .dshwx-where { max-width: 92px; }
|
|
310
|
+
.dshwx[data-fit="phone"] .dshwx-icon,
|
|
311
|
+
.dshwx[data-fit="tiny"] .dshwx-icon { font-size: 14px; }
|
|
312
|
+
.dshwx[data-fit="phone"] .dshwx-temp,
|
|
313
|
+
.dshwx[data-fit="tiny"] .dshwx-temp { font-size: 13px; padding: 5px 7px; margin: -4px -3px; }
|
|
314
|
+
.dshwx[data-fit="phone"] .dshwx-refresh,
|
|
315
|
+
.dshwx[data-fit="tiny"] .dshwx-refresh { padding: 7px; margin: -5px; }
|
|
272
316
|
|
|
273
317
|
/* Very narrow: the place name is the least load-bearing text \u2014 the icon,
|
|
274
318
|
temperature and condition carry the meaning. */
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
319
|
+
.dshwx[data-fit="tiny"] .dshwx-where,
|
|
320
|
+
.dshwx[data-fit="tiny"] .dshwx-sep-where { display: none; }
|
|
321
|
+
.dshwx[data-fit="tiny"] { gap: 6px; }
|
|
322
|
+
|
|
323
|
+
/* Nowhere left to stand. Better absent for the moment a full-width overlay is
|
|
324
|
+
up than a clipped stub sliding under it.
|
|
325
|
+
|
|
326
|
+
visibility, NOT display. A display:none bar has no box, so
|
|
327
|
+
getBoundingClientRect reports zero height, the band measurement has no rows
|
|
328
|
+
to test claimants against and bails \u2014 and the bar would stay hidden forever
|
|
329
|
+
after the overlay that squeezed it went away. A hidden box is still a box. */
|
|
330
|
+
.dshwx[data-fit="none"] { visibility: hidden; pointer-events: none; }
|
|
279
331
|
|
|
280
332
|
/* Coarse pointers (touch) get the larger hit areas at any width. */
|
|
281
333
|
@media (pointer: coarse) {
|
|
@@ -296,10 +348,114 @@ function injectStyles() {
|
|
|
296
348
|
tag.textContent = BAR_STYLES;
|
|
297
349
|
document.head.appendChild(tag);
|
|
298
350
|
}
|
|
351
|
+
var CLAIM_SELECTOR = '[data-dsh-overlay-claim="right"]';
|
|
352
|
+
var BAND_GUTTER = 16;
|
|
353
|
+
function zoomOf(el) {
|
|
354
|
+
const own = el.currentCSSZoom;
|
|
355
|
+
if (typeof own === "number" && own > 0) return own;
|
|
356
|
+
const width = el.getBoundingClientRect().width;
|
|
357
|
+
return el.offsetWidth > 0 && width > 0 ? width / el.offsetWidth : 1;
|
|
358
|
+
}
|
|
359
|
+
function tierOf(width) {
|
|
360
|
+
if (width === void 0) return "full";
|
|
361
|
+
if (width < 200) return "none";
|
|
362
|
+
if (width <= 380) return "tiny";
|
|
363
|
+
if (width <= 520) return "phone";
|
|
364
|
+
if (width <= 720) return "tablet";
|
|
365
|
+
return "full";
|
|
366
|
+
}
|
|
367
|
+
function measureBand(band) {
|
|
368
|
+
const layer = document.querySelector("[data-shell-overlay]");
|
|
369
|
+
const frame = layer?.parentElement ?? null;
|
|
370
|
+
let left = 0;
|
|
371
|
+
let right = window.innerWidth;
|
|
372
|
+
if (frame !== null) {
|
|
373
|
+
const rect = frame.getBoundingClientRect();
|
|
374
|
+
const style = getComputedStyle(frame);
|
|
375
|
+
const zoom = zoomOf(frame);
|
|
376
|
+
left = rect.left + (parseFloat(style.paddingLeft) || 0) * zoom;
|
|
377
|
+
right = rect.right - (parseFloat(style.paddingRight) || 0) * zoom;
|
|
378
|
+
}
|
|
379
|
+
for (const claim of Array.from(document.querySelectorAll(CLAIM_SELECTOR))) {
|
|
380
|
+
const rect = claim.getBoundingClientRect();
|
|
381
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
382
|
+
if (rect.bottom <= band.top || rect.top >= band.bottom) continue;
|
|
383
|
+
if (rect.right < right - 1) continue;
|
|
384
|
+
right = Math.min(right, rect.left);
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
centre: left + (right - left) / 2,
|
|
388
|
+
width: right - left - BAND_GUTTER * 2,
|
|
389
|
+
zoom: frame === null ? 1 : zoomOf(frame)
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function useBandFit(ref) {
|
|
393
|
+
const [fit, setFit] = import_react.default.useState(null);
|
|
394
|
+
import_react.default.useLayoutEffect(() => {
|
|
395
|
+
const el = ref.current;
|
|
396
|
+
if (el === null) return;
|
|
397
|
+
const measure = () => {
|
|
398
|
+
const self = el.getBoundingClientRect();
|
|
399
|
+
if (self.height === 0) return;
|
|
400
|
+
const next = measureBand({ top: self.top, bottom: self.bottom });
|
|
401
|
+
setFit(
|
|
402
|
+
(prev) => prev !== null && Math.abs(prev.centre - next.centre) < 0.5 && Math.abs(prev.width - next.width) < 0.5 && prev.zoom === next.zoom ? prev : next
|
|
403
|
+
);
|
|
404
|
+
};
|
|
405
|
+
measure();
|
|
406
|
+
const layer = document.querySelector("[data-shell-overlay]");
|
|
407
|
+
const frame = layer?.parentElement ?? null;
|
|
408
|
+
const resize = new ResizeObserver(measure);
|
|
409
|
+
if (frame !== null) resize.observe(frame);
|
|
410
|
+
if (layer !== null) resize.observe(layer);
|
|
411
|
+
const scaleWatch = new MutationObserver(measure);
|
|
412
|
+
scaleWatch.observe(document.body, { attributes: true, attributeFilter: ["style", "class"] });
|
|
413
|
+
const mutation = new MutationObserver((records) => {
|
|
414
|
+
if (records.every((record) => el.contains(record.target))) return;
|
|
415
|
+
measure();
|
|
416
|
+
});
|
|
417
|
+
if (layer !== null) {
|
|
418
|
+
mutation.observe(layer, {
|
|
419
|
+
subtree: true,
|
|
420
|
+
childList: true,
|
|
421
|
+
attributes: true,
|
|
422
|
+
attributeFilter: ["style", "data-dsh-overlay-claim", "hidden"]
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (frame !== null) mutation.observe(frame, { attributes: true, attributeFilter: ["style"] });
|
|
426
|
+
window.addEventListener("resize", measure);
|
|
427
|
+
return () => {
|
|
428
|
+
resize.disconnect();
|
|
429
|
+
scaleWatch.disconnect();
|
|
430
|
+
mutation.disconnect();
|
|
431
|
+
window.removeEventListener("resize", measure);
|
|
432
|
+
};
|
|
433
|
+
}, [ref]);
|
|
434
|
+
return fit;
|
|
435
|
+
}
|
|
299
436
|
function WeatherBar() {
|
|
300
437
|
const [state, setState] = import_react.default.useState({ status: "loading" });
|
|
301
438
|
const [busy, setBusy] = import_react.default.useState(false);
|
|
302
439
|
const [unit, setUnit] = import_react.default.useState(loadUnit);
|
|
440
|
+
const ref = import_react.default.useRef(null);
|
|
441
|
+
const fit = useBandFit(ref);
|
|
442
|
+
const shell = {
|
|
443
|
+
ref,
|
|
444
|
+
className: "dshwx",
|
|
445
|
+
"data-fit": tierOf(fit?.width),
|
|
446
|
+
// A non-positive width is the squeezed-out case: the `none` tier hides the
|
|
447
|
+
// bar, and writing a negative max-width would be an ignored declaration
|
|
448
|
+
// that left it at full size behind the overlay.
|
|
449
|
+
//
|
|
450
|
+
// Both lengths are divided by the zoom: `fit` is measured in viewport px
|
|
451
|
+
// and these are author px the zoom scales again (see zoomOf).
|
|
452
|
+
...fit === null || fit.width <= 0 ? {} : {
|
|
453
|
+
style: {
|
|
454
|
+
left: `${fit.centre / fit.zoom}px`,
|
|
455
|
+
maxWidth: `${fit.width / fit.zoom}px`
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
};
|
|
303
459
|
const toggleUnit = () => {
|
|
304
460
|
setUnit((prev) => {
|
|
305
461
|
const next = prev === "C" ? "F" : "C";
|
|
@@ -333,28 +489,29 @@ function WeatherBar() {
|
|
|
333
489
|
fetchWeather().then(setState).catch((e) => setState({ status: "error", error: String(e?.message ?? e) })).finally(() => setBusy(false));
|
|
334
490
|
};
|
|
335
491
|
if (state.status === "loading") {
|
|
336
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
337
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-icon", children: "\u{1F321}\uFE0F" }),
|
|
338
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-label", children: "Loading weather\u2026" })
|
|
492
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ...shell, role: "status", "aria-live": "polite", "aria-busy": "true", children: [
|
|
493
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-icon", "aria-hidden": "true", children: "\u{1F321}\uFE0F" }),
|
|
494
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-label loading", children: "Loading weather\u2026" })
|
|
339
495
|
] });
|
|
340
496
|
}
|
|
341
497
|
if (state.status === "error" || !state.now) {
|
|
342
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
498
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ...shell, "aria-live": "polite", children: [
|
|
343
499
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-icon", children: "\u26A0\uFE0F" }),
|
|
344
500
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-error", title: state.error, children: "Weather unavailable" }),
|
|
345
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `dshwx-refresh${busy ? " busy" : ""}`, title: "Retry", onClick: reload, children: "\u27F3" })
|
|
501
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `dshwx-refresh${busy ? " busy" : ""}`, "data-dsh-no-drag": "", title: "Retry", onClick: reload, children: "\u27F3" })
|
|
346
502
|
] });
|
|
347
503
|
}
|
|
348
504
|
const { icon, label } = describeCode(state.now.weatherCode, state.now.isDay);
|
|
349
505
|
const other = unit === "C" ? "F" : "C";
|
|
350
506
|
const title = `${label} in ${state.where ?? ""} \u2014 feels like ${fmtTemp(state.now.apparentC, unit)}, humidity ${state.now.humidity}%, wind ${Math.round(state.now.windKph)} km/h \xB7 click the temperature for \xB0${other}`;
|
|
351
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
507
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ...shell, title, "aria-live": "polite", children: [
|
|
352
508
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-icon", children: icon }),
|
|
353
509
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
354
510
|
"button",
|
|
355
511
|
{
|
|
356
512
|
type: "button",
|
|
357
513
|
className: "dshwx-temp",
|
|
514
|
+
"data-dsh-no-drag": "",
|
|
358
515
|
onClick: toggleUnit,
|
|
359
516
|
title: `Switch to \xB0${other}`,
|
|
360
517
|
"aria-label": `Temperature ${fmtTemp(state.now.temperatureC, unit)}. Switch to degrees ${other === "F" ? "Fahrenheit" : "Celsius"}.`,
|
|
@@ -384,7 +541,7 @@ function WeatherBar() {
|
|
|
384
541
|
Math.round(state.now.windKph),
|
|
385
542
|
"km/h"
|
|
386
543
|
] }),
|
|
387
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `dshwx-refresh${busy ? " busy" : ""}`, title: "Refresh weather", onClick: reload, children: "\u27F3" })
|
|
544
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `dshwx-refresh${busy ? " busy" : ""}`, "data-dsh-no-drag": "", title: "Refresh weather", onClick: reload, children: "\u27F3" })
|
|
388
545
|
] });
|
|
389
546
|
}
|
|
390
547
|
function apply(ctx) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dennisrongo/dsh-weather",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Weather bar for DeepSeek Harness (dsh) — current conditions at the bottom of the web UI via Open-Meteo, rendered into shell.overlay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"postbuild": "node build/postbuild.mjs",
|
|
30
30
|
"deploy": "node build/postbuild.mjs",
|
|
31
31
|
"typecheck": "tsc --noEmit",
|
|
32
|
-
"test": "node test/smoke.mjs"
|
|
32
|
+
"test": "node build/build.mjs && node test/smoke.mjs"
|
|
33
33
|
},
|
|
34
34
|
"dsh": {
|
|
35
35
|
"bundle": {
|