docs: add detailed change documentation per feature phase
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ae7f0c1a4a
commit
07403d18c4
5 changed files with 717 additions and 84 deletions
79
CONTEXT/00_INDICE.txt
Normal file
79
CONTEXT/00_INDICE.txt
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
===============================================================
|
||||||
|
CONTEXT — INDICE DE DOCUMENTACION
|
||||||
|
Oasis Mobile | Actualizado: 2026-04-28
|
||||||
|
===============================================================
|
||||||
|
|
||||||
|
Lee esto primero. Cada archivo cubre un area.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
FICHEROS DE ESTADO DEL PROYECTO
|
||||||
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
01_PROYECTO.txt
|
||||||
|
Que es el proyecto, stack tecnico, estructura de carpetas,
|
||||||
|
comparacion con el upstream publico, historial de commits.
|
||||||
|
Lee esto al inicio de cualquier sesion nueva.
|
||||||
|
|
||||||
|
03_GIT_ESTADO.txt
|
||||||
|
Estado de las ramas git, commits, archivos modificados,
|
||||||
|
problema de conectividad al remoto, comandos de push.
|
||||||
|
|
||||||
|
04_ARQUITECTURA.txt
|
||||||
|
Arquitectura completa: backend Koa, modelos SSB, las 43 vistas
|
||||||
|
Hyperaxe, sistema CSS, blockchain SSB. Lista todos los archivos.
|
||||||
|
|
||||||
|
05_PROXIMOS_PASOS.txt
|
||||||
|
Tareas pendientes, como correr el proyecto localmente y en
|
||||||
|
Android Studio, como instalar Claude Code en Android Studio.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
FICHEROS DE CAMBIOS (documentan lo que se modifico y por que)
|
||||||
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
02_QR_IMPLEMENTACION.txt
|
||||||
|
Resumen de donde estan los QR codes en la app y que libreria
|
||||||
|
se usa. Indice rapido con numeros de linea.
|
||||||
|
Para detalle tecnico leer: cambio_qr.txt
|
||||||
|
|
||||||
|
cambio_qr.txt
|
||||||
|
Documentacion tecnica completa de los QR codes.
|
||||||
|
- Como funciona cada funcion (renderInvitePage, invitesView,
|
||||||
|
renderInhabitantCard, inhabitantsProfileView)
|
||||||
|
- Por que el IIFE async en invites_view
|
||||||
|
- CSS para los QR en cada tema
|
||||||
|
- Que puede gustar / que puede no gustar del diseño
|
||||||
|
|
||||||
|
cambio_invites_trazabilidad.txt
|
||||||
|
Documentacion del sistema de trazabilidad de invites.
|
||||||
|
- inviteLog en tribes_model.js (estructura, generateInvite, joinByInvite)
|
||||||
|
- SSB message type 'pub-invite' en main_models.js
|
||||||
|
- renderInviteExtra en blockchain_view.js
|
||||||
|
- Que puede gustar / que puede no gustar
|
||||||
|
|
||||||
|
cambio_visual_movil.txt
|
||||||
|
Documentacion de todos los cambios CSS para movil.
|
||||||
|
- mobile.css: safe area, layout, filtros horizontales,
|
||||||
|
botones, QR lightbox, overrides grid inline
|
||||||
|
- OasisMobile.css: por que se reescribio, que se quito
|
||||||
|
- main_views.js: viewport-fit=cover y por que importa
|
||||||
|
- style.css: que se añadio al final
|
||||||
|
- Que puede gustar / que puede no gustar
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
NOTA PARA EL DEVELOPER
|
||||||
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
Los archivos cambio_*.txt estan escritos para que puedas
|
||||||
|
verificar cada decision mirando el codigo.
|
||||||
|
Cada seccion indica archivo y numero de linea aproximado.
|
||||||
|
|
||||||
|
Cosas que puede valer la pena revisar en persona:
|
||||||
|
- El IIFE async en invites_view.js (linea 121) — funciona pero es raro
|
||||||
|
- Los overrides [style*="..."] en mobile.css — fragiles, mejor refactorizar
|
||||||
|
- El inviteLog guardado en el SSB message del tribe — crece con el tiempo
|
||||||
|
- viewport-fit=cover en main_views.js — probar en dispositivo real con notch
|
||||||
|
- El selector div generico que se eliminó en OasisMobile.css — revisar
|
||||||
|
que ningun componente dependa del fondo oscuro de ese selector
|
||||||
|
|
||||||
|
Todo el codigo modificado esta en:
|
||||||
|
nodejs-project/nodejs-project/src/
|
||||||
|
|
@ -1,103 +1,54 @@
|
||||||
===============================================================
|
===============================================================
|
||||||
QR CODES — IMPLEMENTACION (commit 54ad8a1)
|
QR CODES — RESUMEN DE IMPLEMENTACION
|
||||||
|
Ver cambio_qr.txt para la documentacion tecnica detallada
|
||||||
===============================================================
|
===============================================================
|
||||||
|
|
||||||
OBJETIVO:
|
COMMITS: 54ad8a1, b48944e
|
||||||
Añadir QR codes en 3 lugares de la app:
|
DOCUMENTACION COMPLETA: CONTEXT/cambio_qr.txt
|
||||||
1. Invites de tribes (privados y publicos)
|
|
||||||
2. Invites de pubs SSB
|
|
||||||
3. Perfil de usuario (ID SSB como QR para compartir y recibir apoyo)
|
|
||||||
|
|
||||||
LIBRERIA USADA:
|
|
||||||
qrcode ^1.5.4 (ya estaba en package.json, usada en wallet_view.js)
|
|
||||||
Path de require: require('../server/node_modules/qrcode')
|
|
||||||
Metodo: QRCode.toString(data, { type: 'svg' }) -> devuelve SVG como string
|
|
||||||
Render: div({ class: 'qr-code', innerHTML: svgString })
|
|
||||||
|
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
1. TRIBE INVITES — tribes_view.js
|
DONDE ESTAN LOS QR EN LA APP
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
ARCHIVO: nodejs-project/nodejs-project/src/views/tribes_view.js
|
|
||||||
|
|
||||||
CAMBIOS:
|
1. /tribes/generate-invite (POST) -> resultado en renderInvitePage
|
||||||
- Añadido: const QRCode = require('../server/node_modules/qrcode');
|
-> QR del invite code del tribe (hex string 64 chars)
|
||||||
- renderInvitePage() convertida a async
|
-> Archivo: src/views/tribes_view.js, funcion renderInvitePage (linea 101)
|
||||||
- Genera QR del inviteCode y lo muestra bajo el texto del codigo
|
|
||||||
|
|
||||||
FLUJO:
|
2. /invites (GET) -> caja snh-invite-box
|
||||||
Backend: POST /tribes/generate-invite
|
-> QR del invite code del pub publico de la instancia
|
||||||
-> tribesModel.generateInvite(tribeId) -> devuelve inviteCode (string)
|
-> Archivo: src/views/invites_view.js, funcion invitesView (linea 34)
|
||||||
-> renderInvitePage(inviteCode) -> muestra codigo + QR
|
|
||||||
Backend ya tenia: ctx.body = await renderInvitePage(...)
|
|
||||||
|
|
||||||
ANTES:
|
3. /inhabitants (GET) -> tarjeta de cada usuario
|
||||||
exports.renderInvitePage = (inviteCode) => { ... }
|
-> QR del SSB ID, solo visible para el propio usuario (isMe)
|
||||||
|
-> Archivo: src/views/inhabitants_view.js, funcion renderInhabitantCard (linea 83)
|
||||||
|
|
||||||
DESPUES:
|
4. /author/:id (GET) -> perfil de cualquier usuario
|
||||||
exports.renderInvitePage = async (inviteCode) => {
|
-> QR del SSB ID, visible para todos los perfiles
|
||||||
const qrSvg = inviteCode ? await QRCode.toString(inviteCode, { type: 'svg' }) : '';
|
-> Archivo: src/views/inhabitants_view.js, funcion inhabitantsProfileView (linea 233)
|
||||||
// muestra h2 + p con el codigo + div con QR SVG
|
|
||||||
}
|
5. /wallet/receive (GET) -> ya existia antes de esta sesion
|
||||||
|
-> QR de la direccion ECOin
|
||||||
|
-> Archivo: src/views/wallet_view.js (no modificado en esta sesion)
|
||||||
|
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
2. PUB INVITES — invites_view.js
|
LIBRERIA
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
ARCHIVO: nodejs-project/nodejs-project/src/views/invites_view.js
|
qrcode ^1.5.4 (ya estaba en package.json)
|
||||||
|
require: const QRCode = require('../server/node_modules/qrcode');
|
||||||
CAMBIOS:
|
uso: await QRCode.toString(string, { type: 'svg' })
|
||||||
- Añadido: const QRCode = require('../server/node_modules/qrcode');
|
render: div({ class: 'qr-code', innerHTML: svgString })
|
||||||
- invitesView() convertida a async
|
|
||||||
- El bloque snhInvite (caja con invite code del pub) ahora incluye QR
|
|
||||||
|
|
||||||
CONTEXTO:
|
|
||||||
snhInvite se carga desde: src/configs/snh-invite-code.json
|
|
||||||
Estructura: { name: string, code: string }
|
|
||||||
El QR se genera del campo snhInvite.code
|
|
||||||
|
|
||||||
RUTA: GET /invites -> ctx.body = await invitesView({})
|
|
||||||
(backend.js linea 1406 ya tenia await)
|
|
||||||
|
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
3. PERFIL DE USUARIO — inhabitants_view.js
|
CSS CLAVE
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
ARCHIVO: nodejs-project/nodejs-project/src/views/inhabitants_view.js
|
style.css: .qr-code svg, .qr-code-inline svg, .qr-code-profile svg
|
||||||
|
mobile.css: idem + .qr-lightbox-overlay (lightbox sin JS)
|
||||||
CAMBIOS:
|
OasisMobile.css: .qr-code svg rect/path (colores tema oscuro)
|
||||||
- Añadido: const QRCode = require('../server/node_modules/qrcode');
|
|
||||||
- renderInhabitantCard() convertida a async
|
|
||||||
-> QR solo se genera para el propio usuario (isMe === true)
|
|
||||||
-> Aparece debajo del ID SSB en la lista de inhabitants
|
|
||||||
- inhabitantsView() convertida a async
|
|
||||||
-> Usa Promise.all() para renderizar las cards async
|
|
||||||
- inhabitantsProfileView() convertida a async
|
|
||||||
-> QR se genera SIEMPRE (para cualquier perfil visitado)
|
|
||||||
-> Aparece en el bloque inhabitant-left junto al avatar y karma
|
|
||||||
|
|
||||||
RUTAS:
|
|
||||||
GET /inhabitants -> inhabitantsView (lista de usuarios)
|
|
||||||
GET /author/:id -> inhabitantsProfileView (perfil individual)
|
|
||||||
Ambas ya tenian await en backend.js (lineas 1051 y 1066)
|
|
||||||
|
|
||||||
ID SSB formato: @<base64>.ed25519
|
|
||||||
Ejemplo: @abc123...==.ed25519
|
|
||||||
|
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
4. CSS — style.css
|
LO QUE NO SE IMPLEMENTO (pendiente)
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
ARCHIVO: nodejs-project/nodejs-project/src/client/assets/styles/style.css
|
- Boton "Copiar codigo" junto al QR
|
||||||
|
- Lectura de QR por camara (requiere Kotlin nativo)
|
||||||
CLASES AÑADIDAS al final del archivo:
|
- QR en vista individual de cada tribe para compartir URL
|
||||||
.qr-code svg -> max-width 200px, centrado
|
- QR para cada pub activo en la tabla de pubs
|
||||||
.qr-code-inline svg -> max-width 140px (en lista inhabitants)
|
|
||||||
.qr-code-profile svg -> max-width 160px (en perfil)
|
|
||||||
.user-id-qr -> flex column, para agrupar ID + QR
|
|
||||||
.user-id-label -> estilo del texto del ID (monospace pequeño)
|
|
||||||
.invite-code-text -> estilo del codigo de invite (monospace, fondo sutil)
|
|
||||||
|
|
||||||
--------------------------------------------------------------
|
|
||||||
PENDIENTE / POSIBLES MEJORAS:
|
|
||||||
--------------------------------------------------------------
|
|
||||||
- Añadir QR tambien en la vista de cada tribe individual (para compartir la URL)
|
|
||||||
- Permitir escanear QR directamente desde la app (necesita camara nativa)
|
|
||||||
- Añadir boton "copiar codigo" junto al QR de invite
|
|
||||||
- QR para pubs individuales en la tabla de pubs activos
|
|
||||||
|
|
|
||||||
167
CONTEXT/cambio_invites_trazabilidad.txt
Normal file
167
CONTEXT/cambio_invites_trazabilidad.txt
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
===============================================================
|
||||||
|
CAMBIO TRAZABILIDAD DE INVITES
|
||||||
|
(commit 0fc10be)
|
||||||
|
===============================================================
|
||||||
|
|
||||||
|
PROBLEMA QUE RESUELVE:
|
||||||
|
El sistema original no tenia manera de saber:
|
||||||
|
- Quien genero un invite de tribe
|
||||||
|
- Cuando se uso un invite
|
||||||
|
- Quien uso el invite para entrar
|
||||||
|
Para pubs SSB, la aceptacion de invite no dejaba ningun rastro
|
||||||
|
en el log SSB propio.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
PARTE 1 — TRIBE INVITES: inviteLog en tribes_model.js
|
||||||
|
--------------------------------------------------------------
|
||||||
|
ARCHIVO: src/models/tribes_model.js
|
||||||
|
|
||||||
|
ESTRUCTURA inviteLog (nueva):
|
||||||
|
Array de objetos almacenado en el contenido del mensaje SSB del tribe.
|
||||||
|
Cada entrada:
|
||||||
|
{
|
||||||
|
code: string (hex, 64 chars, el mismo que va en tribe.invites[])
|
||||||
|
generatedBy: string (SSB ID del usuario que genero el invite)
|
||||||
|
generatedAt: string (ISO 8601)
|
||||||
|
status: 'pending' | 'used'
|
||||||
|
usedBy: string | null (SSB ID del usuario que entro con el codigo)
|
||||||
|
usedAt: string | null (ISO 8601)
|
||||||
|
}
|
||||||
|
|
||||||
|
FUNCION generateInvite (linea 95):
|
||||||
|
- Genera code con: crypto.randomBytes(INVITE_CODE_BYTES).toString('hex')
|
||||||
|
- Lee el inviteLog actual del tribe (o [] si no existe)
|
||||||
|
- Añade nueva entrada con status 'pending', usedBy/usedAt null
|
||||||
|
- Guarda inviteLog junto al array invites (que ya existia)
|
||||||
|
- El array invites[] sigue existiendo tal cual para la logica de join
|
||||||
|
|
||||||
|
FUNCION joinByInvite (linea 132):
|
||||||
|
- Busca el tribe que tiene ese code en su invites[]
|
||||||
|
- Actualiza el inviteLog mapeando: si entry.code === code, marca
|
||||||
|
status 'used', usedBy = userId, usedAt = now
|
||||||
|
- Elimina el code de invites[] (el code queda consumido, no reutilizable)
|
||||||
|
- Guarda members, invites y inviteLog juntos en una sola llamada updateTribeById
|
||||||
|
|
||||||
|
FUNCIONES getTribeById / listAll:
|
||||||
|
Ambas ya exponían los campos del tribe. Se añadio:
|
||||||
|
inviteLog: Array.isArray(c.inviteLog) ? c.inviteLog : []
|
||||||
|
Si el tribe fue creado antes de este cambio, inviteLog sera [].
|
||||||
|
|
||||||
|
DONDE SE GUARDA ESTO:
|
||||||
|
El inviteLog se guarda en el contenido del mensaje SSB de actualizacion
|
||||||
|
del tribe (via updateTribeById que hace ssb.publish({type:'tribe', ...})).
|
||||||
|
Es decir, el log de invites esta en la blockchain SSB como parte del
|
||||||
|
historial del tribe. NOTA: si alguien lee el SSB log directamente,
|
||||||
|
vera el inviteLog serializado en el campo content del mensaje.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
PARTE 2 — PUB INVITES: SSB message en main_models.js
|
||||||
|
--------------------------------------------------------------
|
||||||
|
ARCHIVO: src/models/main_models.js
|
||||||
|
|
||||||
|
PROBLEMA ORIGINAL:
|
||||||
|
ssb.invite.accept() es una llamada de bajo nivel del protocolo SSB.
|
||||||
|
Hace el handshake de red pero NO publica ningun mensaje en tu log propio.
|
||||||
|
No hay manera de saber despues que aceptaste ese pub invite.
|
||||||
|
|
||||||
|
SOLUCION:
|
||||||
|
Inmediatamente despues del handshake exitoso, publicar manualmente
|
||||||
|
un mensaje SSB en el log propio:
|
||||||
|
|
||||||
|
FUNCION acceptInvite (linea 722):
|
||||||
|
1. Convierte el invite string con toLegacyInvite()
|
||||||
|
2. Hace ssb.invite.accept(code, callback) — el handshake real
|
||||||
|
3. Parsea el code con parseRemote() para extraer host y pubId
|
||||||
|
(parseRemote ya existia en main_models.js, linea 119)
|
||||||
|
4. Publica: ssb.publish({ type: 'pub-invite', pubHost, pubKey, acceptedAt })
|
||||||
|
5. Devuelve el resultado del handshake
|
||||||
|
|
||||||
|
ESTRUCTURA del mensaje SSB publicado:
|
||||||
|
{
|
||||||
|
type: 'pub-invite'
|
||||||
|
pubHost: string | null (ej: 'pub.miservidor.net:8008')
|
||||||
|
pubKey: string | null (ej: '@xxxx.ed25519')
|
||||||
|
acceptedAt: string (ISO 8601)
|
||||||
|
}
|
||||||
|
|
||||||
|
Si parseRemote no puede extraer host o pubId, guarda null.
|
||||||
|
No rompe el flujo (el await del publish usa resolve() en ambos casos).
|
||||||
|
|
||||||
|
DONDE APARECE ESTO:
|
||||||
|
En el blockchain explorer, en el filtro 'invites',
|
||||||
|
y en cualquier lectura del SSB log propio.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
PARTE 3 — BLOCKCHAIN EXPLORER: blockchain_model.js + blockchain_view.js
|
||||||
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
blockchain_model.js — filtro 'invites' (linea 209):
|
||||||
|
filtered = filtered.filter(b => b &&
|
||||||
|
(b.type === 'pub-invite' ||
|
||||||
|
(b.type === 'tribe' && Array.isArray(b.content?.inviteLog) && b.content.inviteLog.length > 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
Muestra:
|
||||||
|
- Todos los mensajes SSB de tipo 'pub-invite'
|
||||||
|
- Todos los mensajes de tipo 'tribe' que tengan inviteLog con entradas
|
||||||
|
|
||||||
|
blockchain_view.js:
|
||||||
|
- FILTER_LABELS: 'pub-invite': 'PUB INVITE', invites: 'INVITES'
|
||||||
|
- CAT_BLOCK2: añadido 'invites' a la categoria de filtros secundarios
|
||||||
|
- TYPE_COLORS: verde (#27ae60) para pub-invite e invites
|
||||||
|
- getViewDetailsAction: case 'pub-invite' -> '/invites'
|
||||||
|
|
||||||
|
renderInviteExtra(block) — funcion nueva (linea 140):
|
||||||
|
Si block.type === 'pub-invite':
|
||||||
|
Tabla de 3 filas: PUB HOST | PUB KEY (user-link) | ACCEPTED AT
|
||||||
|
Si block.type === 'tribe' con inviteLog:
|
||||||
|
Tabla de 6 columnas:
|
||||||
|
CODE (primeros 12 chars + ...) |
|
||||||
|
GENERATED BY (user-link, primeros 16 chars) |
|
||||||
|
GENERATED AT |
|
||||||
|
STATUS (badge pending/used) |
|
||||||
|
USED BY (user-link) |
|
||||||
|
USED AT
|
||||||
|
Retorna null en cualquier otro caso.
|
||||||
|
|
||||||
|
Se llama dentro del render de cada bloque en la vista del explorer.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
QUE PUEDE GUSTAR
|
||||||
|
--------------------------------------------------------------
|
||||||
|
- La trazabilidad de tribes es completa: sabes quien genero, cuando,
|
||||||
|
quien uso, cuando. Todo dentro del propio log SSB.
|
||||||
|
- Para pubs: al menos queda constancia en tu log propio de que
|
||||||
|
aceptaste ese pub invite y cuando.
|
||||||
|
- El inviteLog es retrocompatible: tribes viejas sin inviteLog
|
||||||
|
simplemente lo tienen como [].
|
||||||
|
- El code de tribe se elimina de invites[] al usarse (single-use).
|
||||||
|
- parseRemote ya existia y es la misma funcion que usa otro codigo
|
||||||
|
del modelo, no hay logica duplicada.
|
||||||
|
- Formato ISO 8601 para fechas, legible y ordenable.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
QUE PUEDE NO GUSTAR / PUNTOS DE DISCUSION
|
||||||
|
--------------------------------------------------------------
|
||||||
|
1. El inviteLog se guarda en el mensaje SSB del tribe.
|
||||||
|
Esto significa que cada vez que alguien genera o usa un invite,
|
||||||
|
se publica un nuevo mensaje SSB de tipo 'tribe' con el inviteLog actualizado.
|
||||||
|
Si hay muchos invites, el historial de mensajes de la tribe crece.
|
||||||
|
No es un problema grave pero es algo a tener en cuenta.
|
||||||
|
|
||||||
|
2. El pub-invite SSB message lo publica DESPUES del handshake, no antes.
|
||||||
|
Si el proceso cae justo entre el handshake y el publish, el handshake
|
||||||
|
ocurre pero el log no se actualiza. Es una ventana de inconsistencia pequeña
|
||||||
|
pero existe.
|
||||||
|
|
||||||
|
3. El inviteLog esta en el contenido del tribe message, no en un tipo
|
||||||
|
de mensaje separado. Si alguien actualiza la tribe por otra razon
|
||||||
|
(cambio de descripcion, imagen...) y no incluye el inviteLog,
|
||||||
|
se perderia. Habria que revisar que updateTribeById siempre hace merge
|
||||||
|
de todos los campos del tribe, no solo los que se pasan.
|
||||||
|
|
||||||
|
4. El CODE en la tabla del blockchain se trunca a 12 chars.
|
||||||
|
Es solo visual. El code completo sigue en el dato y en invites[].
|
||||||
|
|
||||||
|
5. No hay manera de revocar un invite pendiente desde la UI.
|
||||||
|
Solo se "consumen" al usarse. Habria que anadir una accion de revocacion.
|
||||||
186
CONTEXT/cambio_qr.txt
Normal file
186
CONTEXT/cambio_qr.txt
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
===============================================================
|
||||||
|
CAMBIO QR — DOCUMENTACION TECNICA
|
||||||
|
(commit 54ad8a1)
|
||||||
|
===============================================================
|
||||||
|
|
||||||
|
OBJETIVO:
|
||||||
|
Añadir QR codes en tres puntos de la app para facilitar
|
||||||
|
el uso en movil: escanear en vez de copiar strings largos.
|
||||||
|
|
||||||
|
LIBRERIA:
|
||||||
|
qrcode ^1.5.4
|
||||||
|
Ya estaba en package.json (usada previamente en wallet_view.js).
|
||||||
|
Path de require dentro de las vistas:
|
||||||
|
const QRCode = require('../server/node_modules/qrcode');
|
||||||
|
Uso: QRCode.toString(string, { type: 'svg' }) -> devuelve SVG como string
|
||||||
|
Render: div({ class: 'qr-code', innerHTML: svgString })
|
||||||
|
innerHTML es un atributo que hyperaxe inyecta directamente como HTML crudo.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
1. TRIBE INVITES — tribes_view.js
|
||||||
|
--------------------------------------------------------------
|
||||||
|
FUNCION MODIFICADA: renderInvitePage (linea 101)
|
||||||
|
|
||||||
|
ANTES:
|
||||||
|
exports.renderInvitePage = (inviteCode) => { ... }
|
||||||
|
|
||||||
|
DESPUES:
|
||||||
|
exports.renderInvitePage = async (inviteCode) => {
|
||||||
|
const qrSvg = inviteCode
|
||||||
|
? await QRCode.toString(inviteCode, { type: 'svg' })
|
||||||
|
: '';
|
||||||
|
// ... div con el codigo + div con innerHTML: qrSvg
|
||||||
|
}
|
||||||
|
|
||||||
|
PORQUE NO REQUIRIO CAMBIOS EN BACKEND:
|
||||||
|
backend.js ya tenia: ctx.body = await renderInvitePage(...)
|
||||||
|
El await ya estaba, hacer la funcion async no rompio nada.
|
||||||
|
|
||||||
|
QUE SE MUESTRA:
|
||||||
|
- Titulo h2 con el nombre de la tribe
|
||||||
|
- p con el invite code en texto
|
||||||
|
- SVG del QR debajo del codigo (clase: 'div-center qr-code')
|
||||||
|
- Si inviteCode es null/undefined -> qrSvg = '' -> el div QR no se renderiza
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
2. PUB INVITES — invites_view.js
|
||||||
|
--------------------------------------------------------------
|
||||||
|
FUNCION MODIFICADA: invitesView (linea 34)
|
||||||
|
|
||||||
|
La funcion padre invitesView ya era async (porque hace otras ops async).
|
||||||
|
El bloque del QR usa un IIFE async para poder hacer await dentro
|
||||||
|
de una expresion inline:
|
||||||
|
|
||||||
|
await (async () => {
|
||||||
|
if (!snhInvite) return null;
|
||||||
|
const qrSvg = snhInvite.code
|
||||||
|
? await QRCode.toString(snhInvite.code, { type: 'svg' })
|
||||||
|
: '';
|
||||||
|
return div({ class: 'snh-invite-box' },
|
||||||
|
h3({ class: 'snh-invite-name' }, snhInvite.name),
|
||||||
|
span({ class: 'snh-invite-code' }, snhInvite.code),
|
||||||
|
qrSvg ? div({ class: 'div-center qr-code', innerHTML: qrSvg }) : null
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
|
||||||
|
POR QUE EL IIFE:
|
||||||
|
La estructura de hyperaxe es funcional (cada elemento es un argumento
|
||||||
|
pasado al elemento padre). No puedes poner un await suelto en medio
|
||||||
|
de una lista de argumentos. El IIFE resuelve eso sin reestructurar
|
||||||
|
toda la funcion.
|
||||||
|
|
||||||
|
ALTERNATIVA MAS LIMPIA (si se quisiera refactorizar):
|
||||||
|
Extraer la caja snhInvite a una funcion separada async
|
||||||
|
y llamarla antes de construir el arbol.
|
||||||
|
El IIFE funciona, pero es menos legible para alguien que no lo conozca.
|
||||||
|
|
||||||
|
DATO DEL CONTEXTO:
|
||||||
|
snhInvite viene de: src/configs/snh-invite-code.json
|
||||||
|
Formato: { name: string, code: string }
|
||||||
|
Es el invite code del pub publico de la instancia.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
3. PERFIL DE USUARIO — inhabitants_view.js
|
||||||
|
--------------------------------------------------------------
|
||||||
|
TRES FUNCIONES MODIFICADAS:
|
||||||
|
|
||||||
|
--- renderInhabitantCard (linea 83) ---
|
||||||
|
async (user, filter, currentUserId)
|
||||||
|
QR SOLO se genera si isMe === true (el usuario viendo su propio perfil).
|
||||||
|
Razon: evitar generar 50 QRs en la lista de inhabitants.
|
||||||
|
|
||||||
|
const isMe = user.id === currentUserId;
|
||||||
|
const qrSvg = isMe && user.id
|
||||||
|
? await QRCode.toString(user.id, { type: 'svg' })
|
||||||
|
: '';
|
||||||
|
|
||||||
|
El QR aparece dentro de 'div.user-id-qr', junto al enlace user-link.
|
||||||
|
|
||||||
|
--- inhabitantsView (linea 177) ---
|
||||||
|
Debia hacerse async porque llama a renderInhabitantCard (que es async).
|
||||||
|
Usa Promise.all para no hacer await uno por uno:
|
||||||
|
|
||||||
|
await Promise.all(inhabitants.map(user =>
|
||||||
|
renderInhabitantCard(user, filter, currentUserId)
|
||||||
|
))
|
||||||
|
|
||||||
|
IMPORTANTE: Solo genera QR para isMe, asi que en la practica
|
||||||
|
solo hay 1 await real de QRCode.toString() en toda la lista.
|
||||||
|
El Promise.all es correcto igualmente para el futuro.
|
||||||
|
|
||||||
|
--- inhabitantsProfileView (linea 233) ---
|
||||||
|
QR se genera SIEMPRE (para cualquier perfil, no solo el propio).
|
||||||
|
Razon: la pagina de perfil individual es de una sola persona,
|
||||||
|
tiene sentido mostrar su QR para que otros lo escaneen y lo sigan.
|
||||||
|
|
||||||
|
const profileId = payload?.id || id || viewedId;
|
||||||
|
const qrSvg = profileId
|
||||||
|
? await QRCode.toString(profileId, { type: 'svg' })
|
||||||
|
: '';
|
||||||
|
|
||||||
|
Aparece en 'div.inhabitant-left' junto al avatar.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
CSS PARA LOS QR
|
||||||
|
--------------------------------------------------------------
|
||||||
|
En style.css (al final del archivo):
|
||||||
|
.qr-code svg -> max-width: 200px, display block, centrado
|
||||||
|
.qr-code-inline svg -> max-width: 120px (en lista inhabitants, mas pequeno)
|
||||||
|
.qr-code-profile svg -> max-width: 150px (en perfil individual)
|
||||||
|
.user-id-qr -> flex column, align-items center
|
||||||
|
.invite-code-text -> font monospace, fondo sutil
|
||||||
|
|
||||||
|
En mobile.css:
|
||||||
|
.qr-code svg -> max-width: 180px
|
||||||
|
.qr-code-inline svg -> max-width: 120px
|
||||||
|
.qr-code-profile svg -> max-width: 150px
|
||||||
|
.qr-lightbox-overlay -> lightbox CSS puro (sin JS), para ampliar QR
|
||||||
|
.qr-lightbox-anchor -> ancla que activa el :target del overlay
|
||||||
|
|
||||||
|
En OasisMobile.css (tema oscuro):
|
||||||
|
.qr-code svg rect -> fill: #1A1A1A (fondo del QR oscuro)
|
||||||
|
.qr-code svg path -> fill: #FFD700 (puntos del QR en dorado)
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
QUE PUEDE GUSTAR
|
||||||
|
--------------------------------------------------------------
|
||||||
|
- No rompe nada: el backend ya tenia await en todas las rutas afectadas
|
||||||
|
- Libreria qrcode ya existia en el proyecto (no se añadio dependencia nueva)
|
||||||
|
- QR solo donde tiene sentido: no hay QRs innecesarios en cada elemento
|
||||||
|
- En lista de inhabitants, QR solo para el propio usuario = sin overhead
|
||||||
|
- SVG directo (no imagen base64 ni PNG), escala perfecto en cualquier pantalla
|
||||||
|
- El QR del perfil es el SSB ID completo (@xxx.ed25519), scaneable con
|
||||||
|
cualquier cliente SSB para seguir al usuario
|
||||||
|
- Lightbox CSS puro para ampliar QR en movil sin JS adicional
|
||||||
|
- Tema oscuro: colores del QR adaptados (#1A1A1A fondo, #FFD700 puntos)
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
QUE PUEDE NO GUSTAR / PUNTOS DE DISCUSION
|
||||||
|
--------------------------------------------------------------
|
||||||
|
1. El IIFE async en invites_view.js es un patron poco comun.
|
||||||
|
Funciona, pero si alguien lee el codigo por primera vez le puede
|
||||||
|
parecer raro. Alternativa: extraer a funcion separada.
|
||||||
|
|
||||||
|
2. El QR en inhabitantsProfileView se genera para TODOS los perfiles.
|
||||||
|
Si se visita el perfil de un usuario con un SSB ID muy largo,
|
||||||
|
el QR es pequeno y puede ser dificil de escanear. Considerar
|
||||||
|
mostrar solo para isMe si se quiere ser mas conservador.
|
||||||
|
|
||||||
|
3. El innerHTML con SVG crudo: hyperaxe lo inyecta tal cual.
|
||||||
|
Si el SVG tuviera algun caracter extraño o fallo de la libreria qrcode,
|
||||||
|
podria romper el HTML. En la practica qrcode es fiable, pero
|
||||||
|
es una inyeccion directa a tener en cuenta.
|
||||||
|
|
||||||
|
4. No hay boton "Copiar codigo" junto al QR.
|
||||||
|
En movil es util para pegar el invite en otro lado.
|
||||||
|
Pendiente de implementar.
|
||||||
|
|
||||||
|
5. El QR del tribe invite code es un string hex aleatorio (32 bytes = 64 chars).
|
||||||
|
No es una URL ni tiene deep link. Solo sirve para copiar/pegar en la app,
|
||||||
|
no para escanear con una app de QR generica de terceros.
|
||||||
|
El del SSB ID si que es estandar y reconocible.
|
||||||
|
|
||||||
|
6. No se ha implementado lectura de QR (solo generacion).
|
||||||
|
Leer QR desde la camara requiere acceso nativo Android.
|
||||||
|
Esto esta fuera del scope del Node.js embebido, habria que
|
||||||
|
hacerlo desde el wrapper Kotlin y pasar el resultado al servidor.
|
||||||
250
CONTEXT/cambio_visual_movil.txt
Normal file
250
CONTEXT/cambio_visual_movil.txt
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
===============================================================
|
||||||
|
CAMBIO VISUAL MOVIL — CSS Y LAYOUT
|
||||||
|
(commit b48944e)
|
||||||
|
===============================================================
|
||||||
|
|
||||||
|
OBJETIVO:
|
||||||
|
Mejorar la experiencia en pantallas moviles (<=768px).
|
||||||
|
El proyecto estaba diseñado principalmente para escritorio.
|
||||||
|
Los cambios afectan tres archivos CSS.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
ARCHIVO 1 — mobile.css (reescrito completo, ~480 lineas)
|
||||||
|
--------------------------------------------------------------
|
||||||
|
RUTA: src/client/assets/styles/mobile.css
|
||||||
|
|
||||||
|
Este archivo se aplica solo en pantallas moviles via media attribute.
|
||||||
|
Antes tenia estilos dispersos y faltaban muchas cosas.
|
||||||
|
Se reescribio completamente.
|
||||||
|
|
||||||
|
SAFE AREA (notch / home indicator Android e iOS):
|
||||||
|
html { -webkit-text-size-adjust: 100%; }
|
||||||
|
body { padding-bottom: env(safe-area-inset-bottom); }
|
||||||
|
.header {
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
padding-left/right: env(safe-area-inset-left/right);
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
Estas variables CSS solo tienen valor si el meta viewport tiene
|
||||||
|
viewport-fit=cover (ver cambio en main_views.js mas abajo).
|
||||||
|
|
||||||
|
FONT SIZE BASE:
|
||||||
|
body { font-size: 14px; }
|
||||||
|
Cabeceras: h1 1.25em, h2 1.1em, h3 1em
|
||||||
|
Textos pequeños (.small, .time, .created-at): 0.78rem
|
||||||
|
|
||||||
|
LAYOUT:
|
||||||
|
.main-content -> flex column (sidebars debajo del contenido)
|
||||||
|
.sidebar-left -> order: 1
|
||||||
|
.sidebar-right -> order: 2
|
||||||
|
.main-column -> order: 3
|
||||||
|
(el contenido principal queda arriba, sidebars debajo)
|
||||||
|
|
||||||
|
Todos los anchos al 100%, min-width: 0 para evitar overflow.
|
||||||
|
|
||||||
|
HEADER:
|
||||||
|
.header -> flex row wrap, padding minimo (4px 2px)
|
||||||
|
.top-bar-left, .top-bar-right -> display: contents
|
||||||
|
(los hijos directos participan en el flex del header)
|
||||||
|
nav ul li a -> padding 5px, font-size 0.82rem
|
||||||
|
|
||||||
|
BOTONES Y TOUCH TARGETS:
|
||||||
|
min-height: 44px (minimo recomendado por Apple HIG / Material)
|
||||||
|
font-size: 15px
|
||||||
|
:active { opacity: 0.75; transform: scale(0.97); } -> feedback tactil
|
||||||
|
|
||||||
|
INPUTS:
|
||||||
|
width: 100%, font-size: 16px
|
||||||
|
(16px evita el zoom automatico en iOS al enfocar un input)
|
||||||
|
|
||||||
|
FILTROS (scroll horizontal):
|
||||||
|
.mode-buttons, .filter-group, .filters, .tribe-mode-buttons:
|
||||||
|
flex-direction: row
|
||||||
|
flex-wrap: nowrap
|
||||||
|
overflow-x: auto
|
||||||
|
scrollbar-width: none (oculta la scrollbar en Firefox)
|
||||||
|
::-webkit-scrollbar { display: none } (oculta en Chrome/WebKit)
|
||||||
|
Cada form dentro: flex-shrink: 0 (no se comprimen)
|
||||||
|
Los botones de filtro: white-space: nowrap, padding 8px 14px
|
||||||
|
|
||||||
|
ANTES: los filtros aparecian en columna vertical, ocupando
|
||||||
|
media pantalla. DESPUES: fila horizontal scrollable invisible.
|
||||||
|
|
||||||
|
IMAGENES DE POSTS:
|
||||||
|
.post img, .feed-card img, .post-image:
|
||||||
|
max-height: 60vh (no ocupan toda la pantalla)
|
||||||
|
width: auto, max-width: 100%
|
||||||
|
border-radius: 8px
|
||||||
|
|
||||||
|
FOTOS DE USUARIO (circulares):
|
||||||
|
.inhabitant-photo, .inhabitant-photo-details:
|
||||||
|
width: 160px, height: 160px
|
||||||
|
border-radius: 50%
|
||||||
|
object-fit: cover
|
||||||
|
margin: 0 auto
|
||||||
|
|
||||||
|
GALERIA:
|
||||||
|
.gallery -> grid 2 columnas (1fr 1fr)
|
||||||
|
|
||||||
|
TRIBES:
|
||||||
|
.tribe-card-image -> height: 160px, object-fit: cover
|
||||||
|
.tribe-section-nav -> overflow-x auto, nowrap (scroll horizontal)
|
||||||
|
.tribe-section-btn -> font-size 11px, white-space nowrap
|
||||||
|
.tribe-details -> flex column
|
||||||
|
.tribe-inhabitants-grid -> 2 columnas
|
||||||
|
.tribe-media-grid -> 2 columnas
|
||||||
|
.tribe-content-grid / .tribe-overview-grid -> 1 columna
|
||||||
|
|
||||||
|
FORUM:
|
||||||
|
.forum-card -> flex column
|
||||||
|
.forum-comment -> margin-left: 0 (no hay espacio para sangria en movil)
|
||||||
|
.comment-body-row -> flex column
|
||||||
|
|
||||||
|
TABLAS:
|
||||||
|
.feed-row, table -> overflow-x auto (scroll horizontal si es ancha)
|
||||||
|
|
||||||
|
QR LIGHTBOX (CSS puro, sin JS):
|
||||||
|
.qr-lightbox-anchor -> display block, width fit-content
|
||||||
|
.qr-lightbox-overlay -> display: none por defecto
|
||||||
|
.qr-lightbox-anchor:target ~ .qr-lightbox-overlay -> display: flex
|
||||||
|
El overlay es position: fixed, fondo negro 88% opacidad, z-index 9999
|
||||||
|
El SVG dentro: max-width min(85vw, 85vh)
|
||||||
|
|
||||||
|
NOTA: este lightbox usa el selector CSS :target, que se activa
|
||||||
|
cuando el hash de la URL coincide con el id del elemento.
|
||||||
|
No requiere JavaScript. Para que funcione, el HTML debe tener
|
||||||
|
el ancla con href="#id" y el overlay con id="id".
|
||||||
|
|
||||||
|
OVERRIDES DE GRID INLINE:
|
||||||
|
Hay vistas del proyecto que ponen grid-template-columns directamente
|
||||||
|
en el atributo style del HTML. Para sobreescribir en movil:
|
||||||
|
[style*="grid-template-columns: repeat(6"] { grid-template-columns: 1fr }
|
||||||
|
[style*="grid-template-columns: repeat(3"] { grid-template-columns: 1fr }
|
||||||
|
[style*="width:50%"] { width: 100% }
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
ARCHIVO 2 — OasisMobile.css (reescrito, solo theming)
|
||||||
|
--------------------------------------------------------------
|
||||||
|
RUTA: src/client/assets/themes/OasisMobile.css
|
||||||
|
|
||||||
|
Antes tenia el CSS de mobile.css entero copiado dentro + el theming.
|
||||||
|
Era ~700 lineas con mucha duplicacion y conflictos.
|
||||||
|
Se reescribio dejando SOLO el theming de colores.
|
||||||
|
|
||||||
|
PALETA DEL TEMA:
|
||||||
|
body: background #121212, color #FFD700
|
||||||
|
header/footer: background #1F1F1F
|
||||||
|
sidebars: background #1A1A1A, border #333
|
||||||
|
inputs/select/textarea: background #333, color #FFD700, border #555
|
||||||
|
buttons: background #444, color #FFD700
|
||||||
|
links: color #FFD700, hover #FFDD44
|
||||||
|
tables: background #222, th #333, even rows #2A2A2A
|
||||||
|
|
||||||
|
SELECTORES ESPECIFICOS (en vez de div generico):
|
||||||
|
Antes habia: div { background-color: #1A1A1A; border: 1px solid #333; }
|
||||||
|
Eso pintaba absolutamente TODOS los divs de la pagina de oscuro,
|
||||||
|
rompiendo layouts con divs transparentes o con otro fondo.
|
||||||
|
Se reemplazo por:
|
||||||
|
.block, .post, .card, .tribe-card, .inhabitant-card,
|
||||||
|
.action-container, .feed-card, .block-single,
|
||||||
|
.invite-page, .wallet-section, .pubs-section,
|
||||||
|
.invites-tribes, .snh-invite-box, .invite-log-block
|
||||||
|
|
||||||
|
SCROLLBAR:
|
||||||
|
::-webkit-scrollbar -> width/height 6px
|
||||||
|
thumb: #555, border-radius 10px
|
||||||
|
track: #1A1A1A
|
||||||
|
|
||||||
|
user-link (badge de usuario):
|
||||||
|
background #FFA500, color #000, bold
|
||||||
|
hover: background #FFD700
|
||||||
|
active: background #cc8400, scale(0.97)
|
||||||
|
focus: background #007B9F (accesibilidad)
|
||||||
|
overflow hidden + text-overflow ellipsis (para IDs largos)
|
||||||
|
|
||||||
|
QR en tema oscuro:
|
||||||
|
.qr-code svg rect -> fill #1A1A1A
|
||||||
|
.qr-code svg path -> fill #FFD700
|
||||||
|
(por defecto la libreria genera QR con fondo blanco y puntos negros)
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
ARCHIVO 3 — main_views.js (una linea cambiada)
|
||||||
|
--------------------------------------------------------------
|
||||||
|
RUTA: src/views/main_views.js
|
||||||
|
|
||||||
|
ANTES:
|
||||||
|
meta({ name: 'viewport', content: toAttributes({
|
||||||
|
width: 'device-width',
|
||||||
|
'initial-scale': 1
|
||||||
|
}) })
|
||||||
|
|
||||||
|
DESPUES:
|
||||||
|
meta({ name: 'viewport', content:
|
||||||
|
'width=device-width, initial-scale=1, viewport-fit=cover'
|
||||||
|
})
|
||||||
|
|
||||||
|
POR QUE IMPORTA viewport-fit=cover:
|
||||||
|
Sin esta directiva, env(safe-area-inset-*) siempre devuelve 0.
|
||||||
|
Es decir, los paddings de safe area del mobile.css no tienen efecto
|
||||||
|
a menos que el viewport tenga viewport-fit=cover.
|
||||||
|
Solo afecta a dispositivos con notch, agujero de camara o barra de
|
||||||
|
gestos de Android. En dispositivos normales no cambia nada visible.
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
ARCHIVO 4 — style.css (añadidos al final)
|
||||||
|
--------------------------------------------------------------
|
||||||
|
RUTA: src/client/assets/styles/style.css
|
||||||
|
|
||||||
|
Se añadieron estilos al FINAL del archivo (no se modifico lo existente):
|
||||||
|
- Fotos circulares (border-radius 50%, object-fit cover)
|
||||||
|
- .invite-page: flex column, align-items center, max-width 500px
|
||||||
|
- .user-id-qr: flex column, align-items center, gap 8px
|
||||||
|
- .invite-code-text: font-family monospace, fondo sutil, padding
|
||||||
|
- .invite-log-block: padding, border-left 3px solido
|
||||||
|
- .invite-status--pending: color naranja
|
||||||
|
- .invite-status--used: color verde
|
||||||
|
- Transiciones en botones: 0.15s ease
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
QUE PUEDE GUSTAR
|
||||||
|
--------------------------------------------------------------
|
||||||
|
- Filtros de scroll horizontal: gran mejora UX en movil. Los filtros
|
||||||
|
ya no ocupan media pantalla.
|
||||||
|
- Safe area funciona correctamente con viewport-fit=cover.
|
||||||
|
- OasisMobile.css limpio: ya no sobreescribe todos los divs del DOM.
|
||||||
|
- Touch targets de 44px: botones faciles de pulsar con el dedo.
|
||||||
|
- Font-size 16px en inputs: evita el zoom automatico de Safari iOS.
|
||||||
|
- Fotos circulares consistentes en toda la app.
|
||||||
|
- El lightbox QR no necesita JavaScript adicional.
|
||||||
|
- Los cambios son aditivos en style.css (no se toco lo anterior).
|
||||||
|
|
||||||
|
--------------------------------------------------------------
|
||||||
|
QUE PUEDE NO GUSTAR / PUNTOS DE DISCUSION
|
||||||
|
--------------------------------------------------------------
|
||||||
|
1. El orden de los sidebars (left=1, main=3, right=2) puede parecer
|
||||||
|
raro. La logica es: sidebar izquierda con navegacion primero,
|
||||||
|
luego sidebar derecha con info secundaria, luego el feed principal.
|
||||||
|
Es discutible, podria ser: left=2, main=1, right=3.
|
||||||
|
|
||||||
|
2. display:contents en .top-bar-left y .top-bar-right:
|
||||||
|
Hace que el div desaparezca visualmente y sus hijos participen
|
||||||
|
en el flex del padre. Es un truco para no reescribir el HTML.
|
||||||
|
Tiene soporte amplio en browsers modernos pero puede ser confuso
|
||||||
|
al depurar el layout con las devtools.
|
||||||
|
|
||||||
|
3. Los overrides de grid inline ([style*="grid-template-columns: repeat(6"])
|
||||||
|
son fragiles: dependen de que el string del style en el HTML sea
|
||||||
|
exactamente ese. Si hay un espacio de mas o menos, no aplica.
|
||||||
|
La solucion correcta seria eliminar los estilos inline del HTML
|
||||||
|
y moverlos a clases CSS, pero eso requiere tocar muchas vistas.
|
||||||
|
|
||||||
|
4. El header en movil con flex-wrap puede quedar diferente segun
|
||||||
|
cuantos elementos de navegacion tenga cada pagina. No hay una
|
||||||
|
maqueta fija, el header se adapta al contenido.
|
||||||
|
|
||||||
|
5. El cambio de viewport-fit=cover afecta a todas las paginas de la app.
|
||||||
|
En dispositivos con notch grande podria mostrar contenido bajo
|
||||||
|
el notch si alguna pagina no tiene los paddings de safe area bien
|
||||||
|
configurados. Hay que verificar en dispositivo real.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue