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:
|
||||
Añadir QR codes en 3 lugares de la app:
|
||||
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 })
|
||||
COMMITS: 54ad8a1, b48944e
|
||||
DOCUMENTACION COMPLETA: CONTEXT/cambio_qr.txt
|
||||
|
||||
--------------------------------------------------------------
|
||||
1. TRIBE INVITES — tribes_view.js
|
||||
DONDE ESTAN LOS QR EN LA APP
|
||||
--------------------------------------------------------------
|
||||
ARCHIVO: nodejs-project/nodejs-project/src/views/tribes_view.js
|
||||
|
||||
CAMBIOS:
|
||||
- Añadido: const QRCode = require('../server/node_modules/qrcode');
|
||||
- renderInvitePage() convertida a async
|
||||
- Genera QR del inviteCode y lo muestra bajo el texto del codigo
|
||||
1. /tribes/generate-invite (POST) -> resultado en renderInvitePage
|
||||
-> QR del invite code del tribe (hex string 64 chars)
|
||||
-> Archivo: src/views/tribes_view.js, funcion renderInvitePage (linea 101)
|
||||
|
||||
FLUJO:
|
||||
Backend: POST /tribes/generate-invite
|
||||
-> tribesModel.generateInvite(tribeId) -> devuelve inviteCode (string)
|
||||
-> renderInvitePage(inviteCode) -> muestra codigo + QR
|
||||
Backend ya tenia: ctx.body = await renderInvitePage(...)
|
||||
2. /invites (GET) -> caja snh-invite-box
|
||||
-> QR del invite code del pub publico de la instancia
|
||||
-> Archivo: src/views/invites_view.js, funcion invitesView (linea 34)
|
||||
|
||||
ANTES:
|
||||
exports.renderInvitePage = (inviteCode) => { ... }
|
||||
3. /inhabitants (GET) -> tarjeta de cada usuario
|
||||
-> QR del SSB ID, solo visible para el propio usuario (isMe)
|
||||
-> Archivo: src/views/inhabitants_view.js, funcion renderInhabitantCard (linea 83)
|
||||
|
||||
DESPUES:
|
||||
exports.renderInvitePage = async (inviteCode) => {
|
||||
const qrSvg = inviteCode ? await QRCode.toString(inviteCode, { type: 'svg' }) : '';
|
||||
// muestra h2 + p con el codigo + div con QR SVG
|
||||
}
|
||||
4. /author/:id (GET) -> perfil de cualquier usuario
|
||||
-> QR del SSB ID, visible para todos los perfiles
|
||||
-> Archivo: src/views/inhabitants_view.js, funcion inhabitantsProfileView (linea 233)
|
||||
|
||||
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
|
||||
|
||||
CAMBIOS:
|
||||
- Añadido: const QRCode = require('../server/node_modules/qrcode');
|
||||
- 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)
|
||||
qrcode ^1.5.4 (ya estaba en package.json)
|
||||
require: const QRCode = require('../server/node_modules/qrcode');
|
||||
uso: await QRCode.toString(string, { type: 'svg' })
|
||||
render: div({ class: 'qr-code', innerHTML: svgString })
|
||||
|
||||
--------------------------------------------------------------
|
||||
3. PERFIL DE USUARIO — inhabitants_view.js
|
||||
CSS CLAVE
|
||||
--------------------------------------------------------------
|
||||
ARCHIVO: nodejs-project/nodejs-project/src/views/inhabitants_view.js
|
||||
|
||||
CAMBIOS:
|
||||
- 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
|
||||
style.css: .qr-code svg, .qr-code-inline svg, .qr-code-profile svg
|
||||
mobile.css: idem + .qr-lightbox-overlay (lightbox sin JS)
|
||||
OasisMobile.css: .qr-code svg rect/path (colores tema oscuro)
|
||||
|
||||
--------------------------------------------------------------
|
||||
4. CSS — style.css
|
||||
LO QUE NO SE IMPLEMENTO (pendiente)
|
||||
--------------------------------------------------------------
|
||||
ARCHIVO: nodejs-project/nodejs-project/src/client/assets/styles/style.css
|
||||
|
||||
CLASES AÑADIDAS al final del archivo:
|
||||
.qr-code svg -> max-width 200px, centrado
|
||||
.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
|
||||
- Boton "Copiar codigo" junto al QR
|
||||
- Lectura de QR por camara (requiere Kotlin nativo)
|
||||
- QR en vista individual de cada tribe para compartir URL
|
||||
- QR para cada pub activo en la tabla de pubs
|
||||
|
|
|
|||
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