feat(F-129): completed feature
This commit is contained in:
94
work/artifacts/F-129/architect.md
Normal file
94
work/artifacts/F-129/architect.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# F-129 — Logs viewer: orden DESC + quitar autoscroll
|
||||
|
||||
## Diagnóstico
|
||||
|
||||
El operador reporta que el visor de logs (`/admin/logs`) muestra los eventos en orden ASC (más antiguo arriba, más reciente abajo). Con el `autoScroll` activo, el usuario ve el stream llegando por abajo, pero si hace scroll arriba para buscar un evento anterior, **el stream salta automáticamente hacia abajo cada vez que llega un nuevo log**, lo que resulta molesto.
|
||||
|
||||
Solicitud:
|
||||
1. Orden **DESC** (evento más reciente arriba)
|
||||
2. Quitar el autoscroll (no hace falta porque el último evento siempre está arriba visible)
|
||||
|
||||
## Diseño
|
||||
|
||||
Usar `flex-direction: column-reverse` sobre el contenedor del log para invertir visualmente el orden DOM sin tocar el array. Esto preserva:
|
||||
- Estado del array en orden de llegada (cronológico) — fácil de gestionar
|
||||
- Cap de 200 líneas: `next.slice(-200)` mantiene las 200 más recientes, que con column-reverse quedan al inicio visible
|
||||
- Sin `bottomRef`, sin `handleScroll`, sin `scrollToBottom`, sin `autoScroll` state
|
||||
|
||||
### Cambios
|
||||
|
||||
`apps/admin/src/components/ServerLogViewer.tsx`:
|
||||
|
||||
```diff
|
||||
- const [autoScroll, setAutoScroll] = useState(true);
|
||||
- const bottomRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
- const isAtBottomRef = useRef(true);
|
||||
|
||||
- const scrollToBottom = useCallback(() => {
|
||||
- if (autoScroll) bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
- }, [autoScroll]);
|
||||
-
|
||||
- const handleScroll = useCallback(() => {
|
||||
- const el = containerRef.current;
|
||||
- if (!el) return;
|
||||
- const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
- isAtBottomRef.current = distFromBottom < 50;
|
||||
- setAutoScroll(isAtBottomRef.current);
|
||||
- }, []);
|
||||
|
||||
- useEffect(() => {
|
||||
- if (status === 'live') scrollToBottom();
|
||||
- }, [logs, scrollToBottom, status]);
|
||||
```
|
||||
|
||||
Toolbar:
|
||||
```diff
|
||||
<div className="flex items-center gap-2">
|
||||
- <label className="flex items-center gap-1.5 text-xs text-gray-400 cursor-pointer">
|
||||
- <input type="checkbox" checked={autoScroll} onChange={...} ... />
|
||||
- Auto-scroll
|
||||
- </label>
|
||||
<button onClick={() => setLogs([])} ... >Limpiar</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
Contenedor de logs:
|
||||
```diff
|
||||
<div
|
||||
ref={containerRef}
|
||||
- onScroll={handleScroll}
|
||||
className="flex-1 overflow-y-auto bg-[#0d1117] font-mono text-xs leading-relaxed"
|
||||
+ className="flex-1 overflow-y-auto bg-[#0d1117] font-mono text-xs leading-relaxed flex flex-col-reverse"
|
||||
style={{ minHeight: 0 }}
|
||||
>
|
||||
<table className="w-full table-fixed">
|
||||
<tbody>
|
||||
{logs.map((entry, i) => ...)}
|
||||
</tbody>
|
||||
</table>
|
||||
- <div ref={bottomRef} />
|
||||
</div>
|
||||
```
|
||||
|
||||
### Cap de 200 líneas
|
||||
|
||||
Se mantiene: `next.slice(-200)` (las 200 más recientes). Con `column-reverse`, las más recientes se ven arriba automáticamente. El usuario puede hacer scroll hacia abajo para ver las más antiguas.
|
||||
|
||||
### Sin autoscroll: por qué es OK
|
||||
|
||||
Con `column-reverse` y array en orden cronológico:
|
||||
- Posición 0 (DOM top) = log más reciente
|
||||
- El usuario SIEMPRE ve el log más reciente arriba sin necesidad de scroll
|
||||
- No hay "salto" del stream que moleste al hacer scroll arriba
|
||||
|
||||
## Sin cambios en backend
|
||||
|
||||
El SSE `GET /admin/logs/stream` sigue emitiendo en orden cronológico. Solo cambiamos la presentación.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Editar `apps/admin/src/components/ServerLogViewer.tsx` — quitar `autoScroll` state, `bottomRef`, `handleScroll`, `scrollToBottom`, useEffect de scroll. Quitar checkbox. Añadir `flex flex-col-reverse` al contenedor.
|
||||
2. `cd apps/admin && npx tsc --noEmit`.
|
||||
3. `cd apps/admin && npm run build`.
|
||||
4. Cerrar gates.
|
||||
20
work/artifacts/F-129/implementer.md
Normal file
20
work/artifacts/F-129/implementer.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# F-129 — Logs viewer: orden DESC + quitar autoscroll
|
||||
|
||||
## Cambios
|
||||
|
||||
### `apps/admin/src/components/ServerLogViewer.tsx`
|
||||
- **Eliminado**: `autoScroll` state, `bottomRef`, `isAtBottomRef`, `scrollToBottom`, `handleScroll`, useEffect que disparaba scroll, checkbox "Auto-scroll" del toolbar.
|
||||
- **Añadido**: `flex flex-col-reverse` al contenedor del log → invierte el orden visual sin tocar el array.
|
||||
- **Resultado**: el log más reciente aparece arriba (DOM top), los más antiguos abajo. El cap de 200 líneas (`next.slice(-200)`) mantiene las más recientes, que con column-reverse quedan visibles arriba.
|
||||
|
||||
## Verificación
|
||||
|
||||
- `cd apps/admin && npx tsc --noEmit` → exit 0.
|
||||
- `cd apps/admin && NEXT_PUBLIC_API_URL=http://192.168.18.93:3000 npm run build` → exit 0.
|
||||
|
||||
## Notas
|
||||
|
||||
- Sin cambios en backend (SSE `GET /admin/logs/stream` sigue emitiendo en orden cronológico).
|
||||
- Sin autoscroll: el log más reciente siempre está visible arriba, así que no hace falta.
|
||||
- Si el operador quiere ver los logs antiguos, hace scroll abajo manualmente. La columna está invertida visualmente pero el DOM sigue en orden cronológico — no hay race conditions ni refs innecesarios.
|
||||
- Operador reinicia admin (`./scripts/monolith.sh prod restart`) para desplegar.
|
||||
17
work/artifacts/F-129/leader-close.json
Normal file
17
work/artifacts/F-129/leader-close.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"verdict": "APPROVED",
|
||||
"agent": "leader",
|
||||
"feature_id": "F-129",
|
||||
"summary": "F-129 listo para commit.",
|
||||
"checks": [
|
||||
"reviewer.json APPROVED",
|
||||
"security.json APPROVED",
|
||||
"qa.json APPROVED",
|
||||
"implementer.md completo",
|
||||
"verify.sh verde",
|
||||
"1 archivo modificado: apps/admin/src/components/ServerLogViewer.tsx"
|
||||
],
|
||||
"commit_message": "feat(F-129): completed feature",
|
||||
"next_step": "operador: ./scripts/monolith.sh prod restart",
|
||||
"closed_at": "2026-08-21T16:11:00Z"
|
||||
}
|
||||
20
work/artifacts/F-129/qa.json
Normal file
20
work/artifacts/F-129/qa.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"verdict": "APPROVED",
|
||||
"reviewer": "qa",
|
||||
"feature_id": "F-129",
|
||||
"summary": "Build limpio, comportamiento esperado.",
|
||||
"checks": [
|
||||
"tsc --noEmit exit 0",
|
||||
"npm run build exit 0",
|
||||
"ServerLogViewer usa flex flex-col-reverse en el contenedor",
|
||||
"No quedan referencias a autoScroll en el componente",
|
||||
"No quedan referencias a bottomRef en el componente",
|
||||
"No quedan referencias a scrollToBottom en el componente",
|
||||
"Toolbar muestra solo botón Limpiar + status indicator + contador de líneas"
|
||||
],
|
||||
"evidence_files": [
|
||||
"apps/admin/src/components/ServerLogViewer.tsx"
|
||||
],
|
||||
"notes": "Tras restart, /logs mostrará el evento más reciente arriba sin auto-scroll.",
|
||||
"reviewed_at": "2026-08-21T16:11:00Z"
|
||||
}
|
||||
17
work/artifacts/F-129/reviewer.json
Normal file
17
work/artifacts/F-129/reviewer.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"verdict": "APPROVED",
|
||||
"reviewer": "reviewer",
|
||||
"feature_id": "F-129",
|
||||
"summary": "Cambios mínimos: column-reverse + quitar autoscroll.",
|
||||
"checks": [
|
||||
"Eliminado autoScroll state, bottomRef, isAtBottomRef, scrollToBottom, handleScroll, useEffect de scroll",
|
||||
"Eliminado checkbox Auto-scroll del toolbar",
|
||||
"Contenedor de log ahora tiene flex flex-col-reverse",
|
||||
"DOM order sigue cronológico; presentación invertida",
|
||||
"Sin cambios en backend",
|
||||
"tsc --noEmit exit 0",
|
||||
"npm run build exit 0"
|
||||
],
|
||||
"notes": "El cap de 200 líneas sigue: next.slice(-200) mantiene las más recientes, visibles arriba con column-reverse.",
|
||||
"reviewed_at": "2026-08-21T16:11:00Z"
|
||||
}
|
||||
13
work/artifacts/F-129/security.json
Normal file
13
work/artifacts/F-129/security.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"verdict": "APPROVED",
|
||||
"reviewer": "security",
|
||||
"feature_id": "F-129",
|
||||
"summary": "Cambio puramente de presentación. Sin impacto de seguridad.",
|
||||
"checks": [
|
||||
"Sin cambios en endpoints ni autenticación",
|
||||
"Sin cambios en el flujo SSE",
|
||||
"Cap de 200 líneas se mantiene (mitigación DoS al cliente)"
|
||||
],
|
||||
"notes": "Riesgo nulo.",
|
||||
"reviewed_at": "2026-08-21T16:11:00Z"
|
||||
}
|
||||
Reference in New Issue
Block a user