diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml
index 18f566d..f657f3e 100644
--- a/.forgejo/workflows/ci.yml
+++ b/.forgejo/workflows/ci.yml
@@ -68,19 +68,14 @@ jobs:
steps:
- uses: actions/checkout@v6
- # Cache node_modules directly (not the npm download cache).
- # npm ci still has to extract + link every module even with a
- # warm download cache, which is where the real time goes. Caching
- # the output directory lets us skip npm ci entirely on hits.
- - name: Cache node_modules
- id: npm-cache
+ - name: Cache npm download cache
uses: actions/cache@v4
with:
- path: frontend/node_modules
- key: node-modules-${{ hashFiles('frontend/package-lock.json') }}
+ path: ~/.npm
+ key: npm-cache-${{ hashFiles('frontend/package-lock.json') }}
+ restore-keys: npm-cache-
- name: Install dependencies
- if: steps.npm-cache.outputs.cache-hit != 'true'
run: npm ci
working-directory: frontend
diff --git a/frontend/src/components/WeatherCard.vue b/frontend/src/components/WeatherCard.vue
index c25cebd..e884199 100644
--- a/frontend/src/components/WeatherCard.vue
+++ b/frontend/src/components/WeatherCard.vue
@@ -85,22 +85,30 @@ const fetchedAtLabel = computed(() => {
Today: {{ weather.today_high }}° / {{ weather.today_low }}°
· {{ tempDelta }}
-
-
- {{ day.day }}
- {{ weatherIcon(day.condition) }}
- {{ day.condition }}
- {{ day.high }}° / {{ day.low }}°
-
- 💧 {{ day.precip_probability }}%
-
-
- 💧 {{ day.precip_mm.toFixed(1) }}mm
-
- 💧 —
- 💨 {{ day.windspeed_max }} {{ weather.wind_unit ?? 'km/h' }}
-
-
+
+
+
+ |
+ |
+ Hi / Lo |
+ 💧 |
+ 💨 {{ weather.wind_unit ?? 'km/h' }} |
+
+
+
+
+ | {{ day.day }} |
+ {{ weatherIcon(day.condition) }} |
+ {{ day.high }}° / {{ day.low }}° |
+
+ {{ day.precip_probability }}%
+ {{ day.precip_mm.toFixed(1) }}mm
+ —
+ |
+ {{ day.windspeed_max }} |
+
+
+
Weather data unavailable — will retry at next slot.
@@ -169,20 +177,31 @@ const fetchedAtLabel = computed(() => {
}
.weather-forecast {
- display: flex;
- gap: 0.75rem;
- overflow-x: auto;
- padding-top: 0.75rem;
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 0.75rem;
border-top: 1px solid var(--color-border);
+ font-size: 0.8rem;
}
-.weather-forecast-day {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.25rem;
- min-width: 4.5rem;
- font-size: 0.8rem;
+.weather-forecast thead th {
+ font-size: 0.68rem;
+ font-weight: 600;
+ color: var(--color-text-muted);
+ text-align: right;
+ padding: 0.5rem 0.4rem 0.25rem;
+ white-space: nowrap;
+}
+
+.weather-forecast thead th:first-child,
+.weather-forecast thead th:nth-child(2) {
+ text-align: left;
+}
+
+.weather-forecast tbody td {
+ padding: 0.3rem 0.4rem;
+ white-space: nowrap;
+ vertical-align: middle;
}
.forecast-day-name {
@@ -190,26 +209,16 @@ const fetchedAtLabel = computed(() => {
}
.forecast-icon {
- font-size: 1.2rem;
+ font-size: 1.1rem;
line-height: 1;
}
-.forecast-condition {
- font-size: 0.7rem;
- color: var(--color-text-muted);
- text-align: center;
- line-height: 1.2;
- word-break: break-word;
-}
-
.forecast-temps {
- white-space: nowrap;
+ text-align: right;
}
-.forecast-precip,
-.forecast-wind {
- font-size: 0.72rem;
- white-space: nowrap;
+.forecast-precip {
+ text-align: right;
color: var(--color-text-muted);
}
@@ -217,6 +226,11 @@ const fetchedAtLabel = computed(() => {
opacity: 0.35;
}
+.forecast-wind {
+ text-align: right;
+ color: var(--color-text-muted);
+}
+
.weather-unavailable {
color: var(--color-text-muted);
font-style: italic;
diff --git a/frontend/src/views/BriefingView.vue b/frontend/src/views/BriefingView.vue
index 53a6c63..77d2144 100644
--- a/frontend/src/views/BriefingView.vue
+++ b/frontend/src/views/BriefingView.vue
@@ -261,22 +261,7 @@ onMounted(async () => {
-
-
-
Weather
-
-
-
-
-
No weather configured
-
-
-
+
{
/>
-
+
+
+
+
+
+
+
Today's News
{{ newsItems.length }} items
@@ -348,7 +344,7 @@ onMounted(async () => {
.briefing-shell {
display: grid;
- grid-template-columns: 1fr 2fr 1fr;
+ grid-template-columns: 1fr minmax(320px, 420px);
grid-template-rows: auto 1fr;
height: 100%;
min-height: 0;
@@ -421,27 +417,10 @@ onMounted(async () => {
}
.btn-trigger:disabled { opacity: 0.5; cursor: not-allowed; }
-/* ─── Left column (Weather) ──────────────────────────────────────────────── */
-
-.briefing-left {
- grid-column: 1;
- grid-row: 2;
- border-right: 1px solid var(--color-border);
- overflow-y: auto;
- padding: 1rem;
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
-}
-
-.briefing-left :deep(.weather-card) {
- margin-bottom: 0;
-}
-
-/* ─── Center column (Chat) ───────────────────────────────────────────────── */
+/* ─── Left column (Chat) ─────────────────────────────────────────────────── */
.briefing-center {
- grid-column: 2;
+ grid-column: 1;
grid-row: 2;
display: flex;
flex-direction: column;
@@ -453,10 +432,10 @@ onMounted(async () => {
min-height: 0;
}
-/* ─── Right column (News) ────────────────────────────────────────────────── */
+/* ─── Right column (Weather + News) ──────────────────────────────────────── */
.briefing-right {
- grid-column: 3;
+ grid-column: 2;
grid-row: 2;
border-left: 1px solid var(--color-border);
overflow-y: auto;
@@ -466,6 +445,10 @@ onMounted(async () => {
gap: 0.5rem;
}
+.briefing-right :deep(.weather-card) {
+ margin-bottom: 0.25rem;
+}
+
.panel-label {
font-size: 0.72rem;
font-weight: 700;
@@ -578,32 +561,21 @@ a.news-title:hover { text-decoration: underline; color: var(--color-primary); }
/* ─── Responsive ─────────────────────────────────────────────────────────── */
-@media (max-width: 900px) {
+@media (max-width: 700px) {
.briefing-shell {
grid-template-columns: 1fr;
- grid-template-rows: auto auto 1fr auto;
- }
- .briefing-header {
- grid-column: 1;
- grid-row: 1;
- }
- .briefing-left {
- grid-column: 1;
- grid-row: 2;
- border-right: none;
- border-bottom: 1px solid var(--color-border);
- max-height: 220px;
+ grid-template-rows: auto 1fr auto;
}
.briefing-center {
grid-column: 1;
- grid-row: 3;
+ grid-row: 2;
}
.briefing-right {
grid-column: 1;
- grid-row: 4;
+ grid-row: 3;
border-left: none;
border-top: 1px solid var(--color-border);
- max-height: 260px;
+ max-height: 300px;
}
}