Al entrar en el repositorio se veia el aviso de rama experimental y, debajo,
el README entero de upstream copiado: 350 lineas contando lo que ya cuenta el
proyecto oficial, y mejor.
Ahora la portada ensena lo que hay aqui: la tabla de que anade esta rama frente
al oficial, Oasis explicado en cuatro dibujos, Karvan, las videollamadas, las
identidades y como empezar desde cero, todo con esquemas propios y capturas
reales.
El aviso de que esto NO es Oasis oficial se mantiene arriba del todo y completo.
Y al final, los enlaces al proyecto de epsylon, que es de donde sale todo.
Las imagenes se enlazan a la wiki en vez de duplicarse en el repositorio, que
es lo que hace tambien upstream con las suyas.
El fichero se sigue leyendo al arrancar el backend (readFileSync sin try/catch),
asi que lo importante es que exista: comprobado que arranca.
stripImageMetadata dependia de sharp, y cuando no esta devolvia la imagen
intacta. En Android sharp no se empaqueta —lleva binarios nativos y el bundle
del movil va sin ninguno— asi que las fotos subian con su EXIF entero: marca,
modelo, numero de serie, fecha y coordenadas GPS. Y sin sharp instalado,
tambien en escritorio.
Ahora hay un limpiador que no depende de nada: recorre el contenedor y tira los
bloques de metadatos sin decodificar ni recomprimir, asi que los pixeles quedan
byte a byte como estaban.
JPEG fuera APP1 (Exif y XMP), APP13 (IPTC), APP14, COM y demas.
Se quedan JFIF y el perfil de color, que afectan a como se ve.
PNG fuera tEXt, zTXt, iTXt, eXIf, tIME, dSIG.
WebP fuera los bloques EXIF y XMP del contenedor RIFF.
De la orientacion se guarda solo eso: sin sharp no se pueden girar los pixeles,
y tirar la etiqueta dejaria tumbadas las fotos verticales. Se reconstruye un
bloque EXIF de 32 bytes con esa unica etiqueta, que no dice nada de quien hizo
la foto ni donde. Si sharp esta, se sigue prefiriendo su camino, que ademas gira
la imagen de verdad.
Comprobado con una foto que llevaba GPS, marca, modelo, numero de serie y fecha:
- En el movil y en escritorio, el blob guardado queda con 32 bytes de EXIF
(solo la orientacion, que sobrevive) y cero rastros de lo demas.
- Los pixeles son identicos antes y despues, en JPEG, PNG y WebP.
- El perfil de color ICC se conserva.
- Sin metadatos, progresivo, GIF, fichero corrupto, fichero vacio y algo que
no es una imagen: se devuelven tal cual, sin excepciones.
Decia "Preparando la aplicacion" sin tilde y "puede tardar unos minutos", que
para un primer arranque de quince segundos suena a que algo va mal. Ahora dice
lo que hace y cuanto: descomprime el servidor, tarda un minuto, no hay que
hacer nada.
Upstream no usa ni un solo subdirectorio en src/views, src/backend, src/models,
src/client/assets/styles ni translations: todo plano. La rama habia creado dos
(views/fork/ y translations/fork/), que es lo que mas se separaba de su forma de
organizar el codigo. Se deshacen, y los ficheros pasan a llamarse como los suyos:
views/fork/hive_nav.js -> views/hive_views.js (como main_views.js)
views/fork/identities.js -> views/identities_view.js (como *_view.js)
translations/fork/i18n_fork.js -> translations/i18n_fork.js (junto a i18n.js)
backend/fork_routes.js -> backend/karvan_routes.js (familia karvan_*)
Ademas se retira una ruta duplicada: /legacy/export estaba declarada dos veces,
en backend.js (la del dev) y en el fichero de rutas de la rama, con el mismo
cuerpo salvo un && defensivo. Nacio cuando el formulario se paso a GET; al
devolverlo a POST quedo como copia exacta. Con ella se van legacyModel y
onboardingModel, que ya no hacia falta inyectar.
La cabecera del fichero de rutas explica ahora por que sigue existiendo, con los
datos medidos y no de memoria: backend.js concentra 625 rutas y se reescribe en
cada release (+833/-286 en la 0.9.2, +266/-31 en la 0.9.1), asi que una ruta
anadida en medio entra en conflicto cada vez.
Quedan diez ficheros propios, todos siguiendo la convencion de upstream, y
+247 lineas repartidas en 21 ficheros suyos (sin contar OasisMobile.css, que es
el tema de la rama).
Comprobado tras mover: arranque, las siete rutas principales, el selector de
identidades con sus siete entradas y su aviso, crear sala y enviar mensaje en
Karvan, y la exportacion de identidad por la ruta del dev.
Al girar el movil aparecia ERR_CONNECTION_REFUSED y ahi se quedaba. La lista de
configChanges estaba incompleta —faltaba smallestScreenSize, que es justo lo que
cambia al rotar—, asi que Android destruia y recreaba la Activity: el WebView se
perdia y volvia a cargar la URL, a veces antes de que el backend escuchara.
Con la lista completa, rotar ya no recrea nada y ademas no se pierde lo que
estuvieras escribiendo.
Y se anade una red de seguridad que no habia: onReceivedError vuelve a esperar
al backend y recarga la ultima pagina que si cargo, en vez de dejar el error de
Chromium en pantalla. Cubre tres casos mas, todos comprobados en el emulador:
- Matar el proceso :node con la app abierta (lo que hace Android cuando necesita
memoria): antes se quedaba muerta, ahora vuelve sola en unos 19 segundos.
- Cambiar de identidad, que reinicia el backend a proposito.
- Arranques lentos en moviles con poca memoria.
Un guard evita que se acumulen hilos esperando al mismo puerto, porque
onReceivedError se dispara una vez por recurso que falle.
Probado ademas: boton atras, modo avion, trim-memory RUNNING_CRITICAL y
am kill con reapertura. Todo eso ya iba bien.
El overlay pisaba menuNetwork, menuMedia y fediverse para dejarlos en Network,
Media y Fediverse. Se quitan: la rama no deberia separarse de upstream en algo
tan visible, y ahora movil y escritorio dicen lo mismo (Community, Library,
Multiverse).
Eso obliga a renombrar el filtro de la topbar: se llamaba Community y ahora ese
nombre es el de una categoria, asi que en la misma pantalla habria dos. Pasa a
ser Social, que es del fork y no choca con nada de epsylon.
Y las traducciones dejan de estar a medias: las 56 claves de la rama estan
completas en los 11 idiomas, ninguna cae al ingles. Antes solo el castellano
estaba entero y los otros nueve mostraban el texto en ingles.
El formulario de exportacion se habia pasado a GET, asi que la contraseña que
cifra la copia de la identidad acababa en el historial del navegador, en el
registro del servidor y en el Referer de lo siguiente que se cargara. Vuelve a
POST, como en upstream, y la ruta la lee del cuerpo.
Comprobado: la exportacion sigue descargando el fichero, y la direccion con la
contraseña ya no responde.
Resto del intento de subtemas propios que se descarto: se copiaba a cada
respuesta pero ninguna vista lo mira. Los subtemas de verdad son los de
upstream, con sus rutas y sus vistas.
Sacar o meter una identidad ya existente lo hace el modulo legacy, que es quien
sabe cifrar el secret con una contraseña. Se enlaza desde la lista para que
este donde se busca.
release.sh hace el ciclo entero y siempre igual, se lance a mano o desde un
runner: empaqueta el backend, compila, verifica y publica la release en gitea.
Las verificaciones no son decorativas, son las que han cazado fallos reales en
esta rama: una APK puede compilar, instalar y arrancar sin el backend dentro, o
haber perdido los permisos de camara y quedarse sin videollamadas. Se comprueba
que estan el backend y libnode.so, los dos permisos, y la firma.
El versionCode sale del numero de commits, que sube solo; el versionName, de
package.json, que es de donde lo saca tambien el backend, para que la version
que muestra la aplicacion no pueda discrepar de la de la APK. Los dos se pasan
por entorno a Gradle en vez de editarlos a mano.
La release se crea como borrador a proposito: publicar una APK firmada sin que
nadie la mire no deberia poder pasar por descuido.
El workflow de gitea no se dispara con cada commit (la APK son mas de 100 MB) y
necesita un runner con el SDK de Android; queda documentado en su cabecera.
La base de multicuenta ya estaba en el arranque (OASIS_ACCOUNT), pero solo se
podia elegir por linea de comandos. Ahora hay una seccion en los ajustes que
lista las identidades, marca cual esta en uso y permite crear una.
Como funciona:
- accounts.js encuentra las identidades buscando directorios ~/.<algo> con un
secret dentro, y lee de el su @id. En Android ese HOME es el almacenamiento
privado de la app, asi que quedan aisladas del resto del sistema.
- El selector anota la elegida en src/configs/active-account; ssb_config.js la
usa al arrancar si no hay OASIS_ACCOUNT (que sigue mandando).
- Crear una identidad solo anota el nombre: el secret lo genera ssb-config al
arrancar con esa cuenta.
En movil hay ademas un boton de reinicio, porque aqui cerrar la app no basta:
el backend vive en un servicio en primer plano que sobrevive. La ruta responde
la pagina y luego termina el proceso; el servicio se recrea y node arranca con
la identidad nueva. No se puede levantar el runtime dos veces en el mismo
proceso, de ahi salir en vez de reiniciar por dentro.
Comprobado en el emulador: al reiniciar, el feed pasa a ser el de la otra
identidad y la anterior sigue intacta.
Dos carpetas pueden tener el mismo secret copiado, y publicar desde las dos
parte el registro sin arreglo posible. La lista lo detecta comparando los @id
y lo avisa en rojo en las dos filas.
La pantalla de ajustes y el asistente de bienvenida piden ux-blocks.png,
ux-chats.png y ux-ainav.png para el selector de modo de interfaz. No se
copiaron al integrar 0.9.5, asi que el selector salia con tres imagenes rotas.
De paso se retira media-favorites.js con su JSON: es de una version anterior
de la rama y ya no lo requiere nadie, upstream lo resuelve con
content_favorites.js.
Cuatro arreglos del envoltorio, todos vistos al probarlo en el emulador:
- onShowFileChooser: sin el, el navegador interno no abre nada al pulsar
"Choose File", asi que no se podia subir ninguna imagen. El APK oficial si
lo implementa; era una regresion frente a el.
- La marca de re-extraccion del backend era el versionName. Al reconstruir la
APK sin subir version, la app se quedaba con el backend anterior y ningun
cambio se aplicaba. Pasa a ser lastUpdateTime del paquete, que cambia en
cada instalacion. (openFd sobre el asset no vale: va comprimido dentro del
APK y no admite descriptor.)
- La espera del backend sondeaba con HEAD, que el backend rechaza con 400.
Ahora abre un socket TCP contra 127.0.0.1:3000, que es lo unico que hay que
saber, y espera hasta cinco minutos en vez de uno: descomprimir 231 MB y
levantar SSB tarda mas en un movil lento. Mientras, pinta una pantalla de
estado con los segundos transcurridos, en vez de dejar el WebView en blanco.
- targetUrl/onNewIntent para poder abrir una ruta concreta desde un intent.
network_security_config.xml tenia base-config detras de domain-config, que el
esquema no admite. Corregido el orden y restaurada la referencia en el
manifiesto, que se habia sustituido por usesCleartextTraffic=true al depurar:
eso abria texto en claro hacia cualquier host, no solo hacia loopback.
Un comentario sin cerrar en karvan.css se comia la llave de la regla que
neutraliza el div{background:#222;padding:20px} global, y con ella todo el
bloque de la videollamada. El resultado eran dos cajas vacias enormes y unos
botones cuadrados en vez del panel.
Ademas:
- La hoja del modulo se cargaba antes que el tema, asi que el tema ganaba por
cascada. Pasa a cargarse despues, que es lo que decia su propio comentario.
- Las opciones de duracion (30m / 2h / 8h) se partian letra a letra en
escritorio; les faltaba white-space: nowrap.
- Clear-SNH usa !important en casi todo, asi que ninguna hoja posterior puede
ganarle. Se resuelve como lo resuelve Oasis: es el tema el que pinta el
modulo. Sin ese bloque las burbujas salian negras sobre blanco y el texto
secundario en amarillo palido, ilegible.
Comprobado con los cuatro temas en escritorio y en el emulador.
buildMessage no copiaba replyTo del contenido del mensaje, asi que la vista
nunca encontraba a quien se respondia y la burbuja salia sin cita. El campo
estaba puesto por error en buildChat, donde no significa nada: un chat no
responde a ningun mensaje.
Se ve al responder a un mensaje y recargar el hilo.
Diagnosticado en el emulador. El backend moria al cargar backend.js:
SyntaxError: Invalid regular expression: /^#([\p{L}\p{N}_-]+)/:
Invalid property name in character class
libnode.so de nodejs-mobile viene sin ICU completo, asi que las propiedades Unicode
en expresiones regulares no existen y el fichero ni siquiera compila. La rama ya lo
habia resuelto con un rango explicito; al integrar 0.9.5 se reintrodujo la forma de
upstream y con ella el fallo. Afectaba a backend.js y a data_model.js.
Tras el arreglo el emulador arranca: 'Backend started on port 3000', 991 modulos,
285 ms de warmup.
Y se devuelven al overlay los nombres de tres categorias. Upstream los renombro en
0.9.5 (Network -> Community, Media -> Library, Fediverse -> Multiverse) y al
restaurar los ficheros de traduccion se colaron los suyos, cambiando las etiquetas
de los hexagonos. Lo destapo una captura de pantalla del usuario, no el codigo.
ssb_config pasa a derivar la cuenta de OASIS_ACCOUNT. Como ssb-config construye la
ruta desde el appname y gui.js saca el socket de config.path, cambiar ese literal
arrastra de forma coherente el almacen, la base de datos, los blobs, el secret y el
socket. Sin la variable se usa 'ssb', asi que las instalaciones existentes siguen en
~/.ssb y no notan nada.
El nombre se valida contra [a-zA-Z0-9][a-zA-Z0-9_-]{0,31} y cae a 'ssb' si no encaja.
No es de adorno: acaba en rutas de fichero y hay operaciones destructivas detras.
Comprobado que OASIS_ACCOUNT=../../etc no sale de sitio.
statePath separa tambien por cuenta cuando se usa OASIS_STATE_DIR. Sin eso, dos
identidades compartirian el estado que 0.9.5 empezo a guardar ahi, que es justo el
camino a partir el registro.
Y el arreglo que importa: doce sitios construian la ruta como os.homedir() + '/.ssb'
en vez de usar la de la cuenta. El peor era panicmode_model, que con varias
identidades habria borrado la equivocada; tambien exportmode y legacy_model, que
habrian exportado o restaurado la identidad de otra cuenta. Los de lectura
—main_models, stats_model y backend.js con gossip.json y ebt— habrian mirado siempre
la cuenta por defecto.
Falta la parte visible: el selector de identidad y el reinicio del proceso al
cambiar. Esta base es la que hacia falta para que eso no sea peligroso.
Verificado: con OASIS_ACCOUNT sin definir la ruta sigue siendo ~/.ssb; con 'trabajo'
pasa a ~/.trabajo; con un valor con .. o espacios se rechaza. Diez rutas OK con la
app en marcha, incluidas /peers y /stats, que son las que leen esos ficheros.
El README abria con 'Oasis Mobile 0.9.1' y describia mejoras sobre 0.9.0, sin decir
en ningun sitio que esto no es el Oasis oficial. Ahora lo primero que se lee es que
es una rama experimental sin afiliacion, con enlace al repositorio de epsylon, la
indicacion de que los fallos se reportan aqui y el enlace a la wiki.
El package.json de la raiz seguia en 0.9.1 mientras el de src/server ya estaba en
0.9.5. Alineados.
Nota: backend.js sirve este README en la interfaz, asi que el aviso se ve tambien
desde dentro de la aplicacion.
renderMobileTopbar era la unica de las tres piezas de interfaz de la rama sin gate,
asi que en escritorio se dibujaba encima de la cabecera de upstream. Los hexagonos y
la barra inferior si lo tenian.
Detectado al arrancar el arbol sin OASIS_MOBILE para preparar la version de
escritorio.
Karvan solo existia en la rejilla de hexagonos, que es de movil. En escritorio no
habia forma de llegar salvo escribiendo la URL.
Se anade renderKarvanLink en main_views, calcado de renderPollsLink —misma forma,
mismo checkMod por getConfig().modules, mismo navLink— y se invoca en el grupo
social junto a renderChatsLink. Entra tambien en modules_view y en la lista de
/modules de backend.js, para poder apagarlo como cualquier otro modulo, con sus
etiquetas en el overlay.
Los estilos del modulo pasan a src/client/assets/styles/karvan.css. Estaban dentro
de OasisMobile.css, que en escritorio no se carga, asi que las salas se habrian
visto sin formato. Va como hoja propia siguiendo el patron de highlight.css, que
tampoco pertenece a ningun tema: asi el modulo se ve igual con cualquier tema y en
las dos plataformas.
Verificado: karvan.css sirve 200 y se carga en las dos plantillas, el enlace aparece
en el menu lateral, y en movil la rejilla de hexagonos sigue igual.
El cliente tenia stun.l.google.com fijo en el codigo. Un servidor STUN aprende la
IP publica y el momento de cada consulta, asi que tal cual estaba cada llamada le
avisaba a Google de que ese usuario esta llamando. En un proyecto con esta postura
sobre privacidad no se sostiene.
Por defecto ya no hay ningun servidor ICE: solo candidatos host, que funcionan en
la misma red y con NAT amable. Es mas honesto degradar a nada que degradar a un
tercero.
Se anade turnCredentials.js, siguiendo el estilo de los helpers de src/backend/:
genera credenciales efimeras con el mecanismo REST de coturn (use-auth-secret),
username = caducidad:identificador y credential = HMAC-SHA1 en base64. El feed id
hace de identificador, que es lo que distingue a quien llama en Oasis. Como coturn
valida el HMAC, las credenciales se emiten sin hablar con el servidor y caducan
solas.
La ruta GET /karvan/ice las sirve solo a loopback —la credencial va firmada con el
secreto del nodo y no debe salir de la maquina— y el cliente la pide ANTES de crear
ninguna RTCPeerConnection, porque la configuracion ICE de una conexion ya creada no
se puede cambiar de forma fiable. Con dos segundos de espera maxima y respaldo a
candidatos host.
La configuracion vive en oasis-config.json bajo rtc, vacia de fabrica, y admite tres
formas: STUN propio, TURN con secreto compartido, o credenciales estaticas. Con
relayOnly se fuerza iceTransportPolicy relay para que ningun par vea la IP del otro.
Verificado: sin configuracion devuelve iceServers vacio; con secreto emite credencial
reproducible y con caducidad; cero referencias a Google en el codigo.
Una revision a fondo del arbol contra 0.9.5 saco varios fallos, unos heredados y
otros que introdujo la propia integracion. Verificados uno a uno antes de tocar.
Recursos referenciados que no existian (los introduje al adoptar vistas de 0.9.5
que los usan, mientras la rama los habia borrado):
- Los cuatro temas *-SNH.css. settings_view ofrece Dark, Clear, Matrix y Purple,
pero solo estaba OasisMobile: elegir cualquiera daba 404 y la app se quedaba sin
estilos. Ahora los cuatro responden 200.
- pdf.min.mjs, pdf.worker.min.mjs y pdf-viewer.js, referenciados desde seis vistas.
Regresiones revertidas a 0.9.5 verbatim en catorce ficheros. Las de fondo:
- larp_model perdio getGoverningPeriodId, que backend.js llama: el anuncio de
gobierno no salia nunca, con el TypeError tragado por un catch.
- courts_view y search_view tenian selected: false, que hyperscript SI serializa
como atributo y HTML interpreta como seleccionado. La forma de upstream, el
spread condicional, es la correcta.
- pm_model perdio includeDeleted, projects_model la validacion de deadline pasado,
jobs_model el desempate por timestamp, middleware el flag de debug HTTP.
forum_view pasa a 0.9.5 conservando el filtro hot. Esto arregla un fallo activo: la
vista de la rama declaraba un cuarto parametro topic y backend.js llama con cinco
argumentos, asi que al abrir una respuesta el %clave del hilo se colaba como tema y
el hilo salia VACIO. Upstream pasa los mismos cinco argumentos a una firma de tres y
JS los ignora, que es lo correcto.
chats_view pasa a 0.9.5 y se implementa replyTo de verdad. Estaba muerto de punta a
punta: backend.js no leia ctx.query.replyTo ni pasaba el cuarto argumento a
sendMessage, asi que ningun mensaje llegaba a tener replyTo y la cita no se
renderizaba jamas. Ahora la cadena esta completa —modelo, GET, POST y vista— con
indice msgById armado antes de mezclar con las encuestas, y sin gate de movil:
responder a un mensaje no es una funcion de movil.
aria-label: encontrada la causa. No es que hyperaxe lo ignore, es que hyperscript
solo asigna como propiedad y html-element solo serializa atributos de su tabla, en
la que aria-* no esta; data-* si tiene rama propia, por eso las tablas responsive
funcionaban. Se arregla con el escape hatch attrs:{}, sin tocar node_modules.
prepare.sh copia README.md y falla si no esta: backend.js lo lee con readFileSync
sin try/catch, de modo que sin el fichero el backend muere con ENOENT al arrancar.
Verificado con la app en marcha: 17 rutas OK, los cuatro recursos que faltaban
sirviendo 200, aria-label emitido en el HTML, e interfaz intacta (10 categorias,
50 modulos, 7 accesos rapidos, 0 etiquetas vacias).
main.js resuelve el directorio de datos con process.env.HOME || resolve(__dirname,
'..','..'). En Android HOME suele llegar como "/", que no es escribible, asi que el
servicio lo fija explicitamente con Os.setenv al almacenamiento privado de la app:
ahi cuelga ~/.ssb. Tambien fija TMPDIR.
prepare.sh deja de pedir un npm install: saca el node_modules del APK base, que
viene podado —137 MB frente a los 1,4 GB de una instalacion de escritorio— y
sustituye solo el codigo. Es seguro porque las 98 dependencias son identicas entre
0.9.1 y 0.9.5: ninguna nueva, ninguna quitada, ninguna con version distinta.
El zip se comprime; sin comprimir el backend son 145 MB y la APK se iria por encima
de 200.
APK generada y verificada: 78 MB, net.laenre.oasis, versionName 0.9.5, targetSdk 35,
con libnode.so y el backend de 32 MB dentro.
Hasta ahora la APK salia re-firmando la de epsylon, y su classes.dex no implementa
WebChromeClient.onPermissionRequest. Sin ese metodo el WebView deniega por defecto
todo getUserMedia, asi que las videollamadas de Karvan no podian funcionar por mucho
que se parchease el manifest: la lista de permisos es estatica y se fija al compilar.
El wrapper cierra las dos puertas. Declara CAMERA, RECORD_AUDIO y
MODIFY_AUDIO_SETTINGS, pero no se conceden al instalar: son permisos peligrosos y se
piden en tiempo de ejecucion la primera vez que se pulsa Llamar. Si nadie llama, no
se pide nada. uses-feature required=false evita excluir dispositivos sin camara.
Reutiliza las tres librerias nativas tal cual: libnode.so es nodejs-mobile v18.20.4
oficial y libnative-lib.so son 6,8 KB con un unico simbolo JNI. De ahi que el
namespace sea com.solarnethub.oasis —JNI resuelve por nombre— mientras el
applicationId es net.laenre.oasis, que permite instalarla junto a la oficial. No
hace falta NDK ni compilar Node.
De paso corrige cosas del APK actual: targetSdk 35, trafico en claro permitido solo
hacia loopback en vez de usesCleartextTraffic global, allowBackup a false para que
adb backup no saque el .ssb, y el backend en un servicio en primer plano en el
proceso :node, que ademas es lo que permitira reiniciarlo limpio para el cambio de
identidad. El APK actual declara FOREGROUND_SERVICE pero no registra ningun servicio,
de modo que el backend muere al pasar a segundo plano.
Los binarios no se versionan: scripts/prepare.sh extrae las librerias y empaqueta el
backend. La keystore se pasa por entorno.
Verificado: ./gradlew assembleDebug construye una APK de 52 MB con las tres librerias,
targetSdk 35 y los permisos declarados; su DEX contiene onPermissionRequest,
PermissionRequest, CAMERA y RECORD_AUDIO, donde el APK actual tiene cero de los cuatro.
Falta probarla en un dispositivo real.
Sin JS el chat ya funcionaba —el formulario publica y el mensaje sale en el HTML—
pero no llegaban los mensajes de los demas hasta recargar a mano.
La pagina de sala anade <noscript><meta http-equiv=refresh content=10>: solo actua
cuando no hay JavaScript, asi que con JS el data-channel sigue mandando y no hay
recargas encima del chat en vivo. Es el mismo recurso que usa upstream en
indexing_view.
Comprobado ademas sobre el modelo, con TTL acortado: la sala se autodestruye al
vencer y no deja mensajes, el anillo respeta el tope de 250 conservando los mas
recientes, se rechaza la senalizacion de mas de 16 KiB, el tope de 100 salas se
aplica, y postear en una sala inexistente devuelve null en vez de reventar.
El identificador de sala son 72 bits de entropia: no es adivinable, y es la unica
credencial de acceso — quien tiene el enlace, entra. Es el modelo previsto, pero
conviene tenerlo escrito.
polls, blogs y data tenian modelo, vista y rutas desde la integracion de 0.9.5,
pero no aparecian en ningun sitio: habia que llegar por URL.
Entran en los hexagonos por categoria — blogs junto a menciones y publicaciones,
encuestas junto a votaciones, coincidencias en herramientas — y en la lista de
modulos fijables en la barra inferior. La rejilla se autogestiona, asi que no hace
falta tocar CSS.
Sus etiquetas ya existian en el i18n de upstream (pollsTitle, blogTitle, dataTitle),
de modo que salen traducidas sin anadir claves al overlay.
Verificado: 10 categorias y 50 modulos (antes 47), los tres enlaces presentes en la
home, 0 etiquetas vacias y las cuatro rutas respondiendo 200.
backend.js pasa a la version de 0.9.5 y baja de 1549 a 16 lineas de divergencia.
Sin esto los modulos que el dev ha anadido estaban portados pero inaccesibles: no
tenian rutas. Ahora /polls, /blogs y /mentions responden.
Reinyectado de la rama: el enganche de fork_routes, los tres gates de movil (no
abrir navegador al arrancar, no matar el sbot al borrar la cadena), el selector de
chats de la barra lateral movil, y los globals de la interfaz — el filtro
Personal/Community, la visibilidad de los hexagonos y el avatar de la topbar.
Esos globals no aparecian al clasificar el diff y su perdida dejaba la home sin
hexagonos; detectado al comparar el HTML servido.
bottomBarPickerView (la pantalla de personalizar la barra) vivia en main_views y se
perdia al adoptar 0.9.5. Pasa a fork/hive_nav.js, junto al resto de la interfaz de
la rama, y main_views la reexporta en una linea.
housing se porta pero apagado (housingMod off). Mantenerlo fuera obligaba a editar
83 referencias de backend.js en cada release; asi el fichero queda igual que
upstream y el modulo no aparece en la interfaz. Se portan tambien contentPdf,
housing_model y housing_view para cerrar los requires.
Verificado con la app en marcha: 29 rutas OK, hexagonos 10/47 con los filtros
Personal y Community dando 5 y 5, topbar con avatar, 7 accesos rapidos, orden de
hojas correcto, y Karvan creando sala y sirviendo el panel de videollamada.
style.css pasa a 0.9.5: lo unico que la rama le habia hecho era borrar reglas de
housing, y su estilo propio vive en OasisMobile.css, que carga despues y gana igual.
Se adopta 0.9.5 en tags_view (anade busqueda y filtros mine/recent), vote_view,
market_view y activity_view, donde la rama solo arrastraba la version anterior:
renderContentActions de dos parametros, comentarios de mercado propios y un import
muerto de renderHiveNav que no se llegaba a llamar.
Quedan a proposito en la version de la rama forum_view y chats_view: llevan
funcionalidad movil propia (la tira de temas del foro y la interfaz de responder
mensajes, que acompana al replyTo del modelo) entrelazada con estructuras que 0.9.5
ha reescrito. Integrarlas pide rehacer esa funcionalidad sobre la forma nueva, y no
es un cambio que convenga colar entre otros veinte.
Verificado con la app en marcha: 21 rutas OK, 0 claves i18n rotas, 0 requires rotos,
interfaz intacta y orden de hojas correcto (style.css, mobile.css, OasisMobile.css).
main_views.js pasa a la version de 0.9.5 y se le reinyectan los seis puntos propios:
el enganche a fork/hive_nav, la topbar y los hexagonos en la cabecera, la barra
inferior antes del pie, el orden invertido de hojas de estilo en movil (en los dos
templates), el campo para copiar el Oasis ID y el chip de dispositivo.
Esto era necesario, no cosmetico: el renderContentActions de la rama tenia dos
parametros y el de 0.9.5 tiene tres. Las vistas ya integradas lo llaman con el
tercero (favoritos, difusion, reportar) y la rama lo ignoraba en silencio, de modo
que esos botones no llegaban a dibujarse.
Cuidado con el orden de hojas: 0.9.5 carga el tema antes de mobile.css, lo que en
movil dejaba a mobile.css ganando por cascada. Corregido en los dos templates.
bookmark_view pasa a 0.9.5 con el renderPMButton de la rama reinyectado. El
renderFavoriteToggle propio desaparece porque 0.9.5 integra los favoritos dentro de
renderContentActions, que cubre lo mismo.
Verificado con la app en marcha: 15 rutas OK, orden de hojas correcto en el HTML
servido (style.css, mobile.css, OasisMobile.css), 10 categorias y 47 modulos en los
hexagonos, topbar con Personal/Community y avatar, 7 accesos rapidos, campo de Oasis
ID presente y 0 etiquetas vacias.
chats_model: se adopta 0.9.5 y se reinyecta el replyTo de la rama (responder a
mensajes). El fix del salt en joinByInvite ya venia en upstream, y el limite de
mensajes pasa a ser configurable (60/h) en vez del parche que lo saltaba en movil.
config-manager: version de 0.9.5 con karvanMod, los seis modulos de public y toda
la normalizacion de bottomBarPins reinyectados; housingMod fuera. Entran blogsMod
y pollsMod del dev.
Se adopta 0.9.5 en votes_model (ya trae el margen de 2 minutos de la rama y anade
codigos de error), blobHandler (superconjunto compatible: mantiene handleBlobUpload
y anade subida multiple y deteccion de OGG), market_model (misma refactorizacion
mas opiniones y compradores), agenda_model y agenda_view (el codigo de 0.9.5 ya
degrada solo sin housingModel), opinions_view, cv_view y trending_view.
peers_view y parliament_view se quedan como estan: sus celdas llevan data-label,
que es lo que OasisMobile.css usa para convertir las tablas en tarjetas en movil.
Verificado con la app en marcha: 20 rutas OK, interfaz intacta (10 categorias, 47
modulos, 7 accesos rapidos, 0 etiquetas vacias) y Karvan completo de punta a punta
— crear sala, enviar y leer mensajes, mailbox de senalizacion WebRTC con miembros,
y la pagina de sala sirviendo karvan.js con el panel de videollamada.
Segunda tanda. Resueltas por merge de tres vias 15 divergencias que no solapaban,
y aplicada la version de 0.9.5 en 19 vistas y modelos donde la rama solo arrastraba
codigo antiguo (firmas viejas de renderProductForm, renderJobForm, listTags sin
search, y el import sin renderSpreadEditWarning). Se preserva la eliminacion de
housing donde la rama lo habia descartado: modules_view, blockchain_view,
search_model y tags_model.
Modulos nuevos del dev portados: blog, data, gallery, mentions, polls, workflows,
comments, recurrence y media_gallery, mas contentPdf, pdfDocument y content_favorites.
main_views recupera renderEngagement, spreadsFor y renderSpreadEditWarning, que la
rama habia eliminado y que las vistas de 0.9.5 necesitan. Sin ellas polls_view
lanzaba TypeError al editar.
El overlay de i18n pasa de 15 a 41 claves (46 en castellano): incorpora las 9 que
upstream tenia en 0.9.1 y elimino en 0.9.5 mientras nuestro codigo las sigue usando
(menuBlogs, spreadHint, chatShareUrl, forumFilterHot, publishBlog...) y las 17 de
Karvan, que nunca existieron y tiraban de texto de reserva en ingles; ahora estan
traducidas al castellano.
Verificado con la app arrancada: declara 0.9.5, 0 claves i18n sin resolver (antes
26), 26 rutas responden 200/302, y la interfaz de la rama sale intacta: 10
categorias y 47 modulos en los hexagonos, topbar con Personal/Community y avatar,
7 accesos rapidos en la barra inferior y 0 etiquetas vacias (antes 1, la de Blogs).
Primera tanda de la integracion 0.9.1 -> 0.9.5. Se actualizan los 57 ficheros que
upstream cambio y la rama no tocaba, entre ellos los 11 de traduccion: eso ya solo
es posible gracias al overlay de i18n, antes eran 11 conflictos garantizados.
Se portan ademas las dependencias que 0.9.5 introduce y de las que dependen los
ficheros actualizados: pdfDocument, content_favorites, recurrence, media_gallery,
comments_view, gallery_view y polls (modelo, limites y vista).
Quedan 48 ficheros con divergencia real por resolver a mano (backend.js,
main_views.js, OasisMobile.css y 45 vistas y modelos).
Verificado: 0 errores de sintaxis en src/, 0 requires relativos rotos, la app
arranca declarando 0.9.5 y las 47 rutas principales responden 200/302.
Quedaban tres rutas propias sueltas dentro de la cadena de upstream:
/settings/bottombar (GET y POST, la personalizacion de la barra inferior) y
/legacy/export en GET, que upstream solo tiene en POST.
Con esto backend.js queda sin ninguna ruta de la rama: 8899 lineas frente a las
9067 de partida, y el unico rastro son las 4 lineas del require de fork_routes
en el array de middleware.
Verificado: el router monta las 11 rutas y backend.js no conserva referencias
a identificadores movidos.
backend.js concentra las 577 rutas de upstream en una sola cadena fluida y se
mueve +905/-298 lineas por release. Las 8 rutas de Karvan, su modelo, el import
de vistas y las tres funciones de relay cross-device vivian intercaladas ahi,
asi que cada integracion las ponia en conflicto.
Pasan a src/backend/fork_routes.js, un router propio que se inserta en la cadena
de middleware justo antes del router de upstream; lo que no casa cae a next().
Las dependencias (cooler, pull, pmModel, checkMod, getViewerId) se inyectan en
vez de reconstruirse, para no abrir un segundo cliente muxrpc contra el sbot.
backend.js pasa de 9067 a 8932 lineas. Quedan 2 lineas de enganche en lugar de 137.
Verificado: el router monta las 8 rutas (/karvan, /karvan/create, /karvan/:id,
/karvan/:id/invite, /karvan/:id/msg, /karvan/:id/msgs y /karvan/:id/signal en GET
y POST) y no quedan referencias huerfanas en backend.js.
Con el overlay en su sitio, la rama ya no necesita editarlos. Los 11
oasis_*.js quedan identicos a upstream 0.9.1, asi que al integrar una version
nueva se copian tal cual en lugar de resolver conflictos sobre ~13.000 lineas.
El overlay recoge ademas los 6 textos del grupo spread en castellano que la
rama corregia dentro de oasis_es.js.
Efecto lateral asumido: vuelven las 86 claves de modulos que la rama no usa.
Son texto muerto, no cambian comportamiento.
Verificado: las claves propias resuelven en los 11 idiomas y la interfaz
renderiza identica (419 / 799 / 7799 bytes en topbar, barra inferior y hexagonos).
main_views.js es el fichero de upstream con mas movimiento despues de backend.js
(+256/-502 lineas de 0.9.1 a 0.9.5). La interfaz propia de la rama vivia dentro,
asi que cada integracion de una version nueva la ponia en riesgo.
renderMobileTopbar, renderBottomBar, renderHiveNav y PINNABLE_MODULES pasan a
src/views/fork/hive_nav.js, una factory que recibe i18n por referencia (main_views
lo muta sin reasignarlo). En main_views quedan 8 lineas de enganche en lugar de 168.
Verificado que el HTML renderizado es identico byte a byte antes y despues, en las
tres funciones y con los filtros personal, community y sin filtro.
Las 15 claves anadidas por la rama (barra inferior bb*, menu Personal/Community,
karvanTitle, peerLastChange, filter) vivian editadas dentro de los 11
src/client/assets/translations/oasis_*.js. Upstream reescribe esos ficheros en
cada release (de 0.9.1 a 0.9.5 elimino 849 claves), asi que cada version las
ponia en conflicto.
Ahora estan en translations/fork/i18n_fork.js y se fusionan desde i18n.js. Los
11 ficheros de idioma pueden reemplazarse por los de upstream sin perder nada.
Verificado cargando i18n.js con los oasis_*.js de 0.9.5 sin modificar: las 15
claves resuelven en los 11 idiomas y las nuevas de upstream siguen disponibles.
Makes a Karvan room work between two different phones once they're connected to a pub:
- karvan_model: rooms track remoteFeeds (SSB ids of other participants); addRemoteFeed/
getRemoteFeeds/anyRemoteRooms (feed-id validated, capped at MAX_MEMBERS).
- backend: inviting registers the invitee's feed; joining by link registers the inviter's
feed (read from the invite PM). Posting a message publishes a private 'karvan-relay' to
those feeds; an efficient LIVE log-stream subscriber (createLogStream old:false live:true)
ingests incoming relays and injects them into the local mirror room (dedup by mid,
skips own → no loop). WebRTC SIGNAL frames are NOT relayed (too many/slow for the log).
- Chosen over a muxrpc plugin because a classic pub only replicates the LOG (store-and-forward);
live muxrpc wouldn't reach the other phone. No SSB-startup changes → no boot risk.
Verified: boot OK with the live stream, invite/adopt register feeds, message posts + relay
publishes, server stays up, 16/16 tests. Cross-device delivery needs the user's 2-phone+pub test.
Note: text chat only — video still needs the wrapper camera permission (Fase 3).
Dead code (from the abandoned thumb-zone/Explore-sheet nav experiment):
- OasisMobile.css: removed ~90 lines of orphaned CSS (.oasis-bottombar-fork/.bb-fab-*,
.hive-sheet*/.fork-sheet*/.hsq-*, .omt-spacer, .hive-sheet-seg/.hs-seg, dead
.oasis-bottombar-fixed) — no element emits any of them.
- main_views.js: removed renderHiveSheet() (never called; sole emitter of that CSS).
- karvan_view.js: dropped dead export karvanShortId + unused karvanView param.
- Added a style for .karvan-msg-live (client emitted it with no rule).
Karvan security (from the review):
- GET /karvan/:id only adopts a mirror room on a real navigation (sec-fetch-dest
document / Accept text/html), not on <img>/subresources → fixes a CSRF that could
spam/evict the user's ephemeral rooms.
- Cap room.members at 50 (was unbounded; each poll echoed it back).
- Reject signal payloads >16KB (SDP/ICE are tiny) — anti memory-DoS.
- karvan.js: guard malformed {kind:desc} signals so one bad signal can't abort a poll batch.
Tests: 15/15 (added members-cap + oversized-payload). Verified: CSRF fix (nav=200,
subresource=302), pages unchanged after CSS removal, boot clean.
Reuses Oasis's own private-message invite pattern (like the industry module:
pmModel.sendMessage([feed], 'KARVAN_INVITE', '... -> /karvan/<id>')), so the invite
lands in the contact's inbox with a link — no new SSB code, no touching the SSB
startup. Joining a room by link adopts a local mirror (karvanModel.adoptRoom, the
id is the capability). Real-time cross-device signaling (media/text sync between
the two mirrors) is the remaining Fase 2 piece (muxrpc ephemeral), deferred.
Verified single-node: invite publishes to the inbox, validation, adopt, form; 13/13
model tests (added adoptRoom).
Oasis ships no test runner, so this is a dependency-free node script (node
test/karvan_model.test.js). Covers rooms, ephemeral messages, the 250-msg ring
buffer, truncation/validation, self-destruct by idle+absolute TTL, idle reset on
activity, MAX_ROOMS eviction, and the WebRTC signaling mailbox. Lives in test/
(outside src/), so it is not bundled into the APK.
Adds a call panel to the Karvan room (local video + remote gallery + call/mic/cam/hang-up
buttons) and grafts getUserMedia + addTrack + ontrack onto the peer connections we already
use for the text data-channel (perfect negotiation handles the renegotiation). No backend,
no new !important. Verified: getUserMedia path attaches audio+video tracks to the local
video and activates the panel (chromium fake device). getUserMedia will fail gracefully on
the current wrapper (no camera/mic permission) with a clear message — the chat keeps working.
Fases 2 (SSB cross-device signaling) and 3 (wrapper permissions) pending.
The hexagon hive was rendered in the header on EVERY page (~210px), pushing real
content far down on Peers/Market/Inbox/etc. Now a middleware flag
(__OASIS_SHOW_HIVE__ = path is /activity or /) gates renderHiveNav so the hive is
the home hub only; other pages get just the fixed topbar and their content up top.
Personal/Community and the bottombar still navigate everywhere.
Per feedback (avoid !important, substitute the CSS). Verified with computed-style
diffs on the real forum/chat DOM — layout is byte-identical.
- OasisMobile.css: 45 -> 23 !important. Removed the ones that only fought epsylon's
NARROW rules; win by load-order/specificity instead (e.g. .forum-score-box
.forum-score-form, .main-column .new-message-form). Kept the truly load-bearing
ones (inline-style overrides [style*=...], round buttons vs the broad
button{min-height:44px!important}, and comment-body-row vs mobile.css:164).
- mobile.css (FORK ONLY, user-authorized): removed !important from 7 narrow
forum/chat rules (.forum-comment margin/padding, .comment-body-row flex-dir,
.comment-vote/text-col width, .forum-score-* flex, .comment-textarea width) so
our theme wins without !important. Not touched on UX_OASIS.
Per feedback: bring the bottom bar back to the design that was liked several
versions ago — user-pinned modules, an edit pencil to /settings/bottombar, and a
fixed Peers/Invites pair on the right. The central + FAB is removed. The hive
returns to the header (top) so Personal/Community filter the visible hexagons; the
Explore bottom-sheet is retired. Karvan module kept.
The top bar was left empty (logo + avatar only) after moving the filter to the
Explore sheet — it looked unbalanced/broken. Since the sheet no longer blocks the
top (visibility:hidden), Personal/Community are brought back to the top bar where
they fill the space and now work; the duplicate segmented control is removed from
the Explore sheet.
Navigation fixes:
- The bottom "Explore" sheet no longer blocks the top bar: closed state is
visibility:hidden (out of hit-testing), so the top identity buttons and page
content always receive taps (fixes "top buttons don't work").
- Top bar is now identity only (logo, avatar). The Personal/Community filter
moved into the Explore sheet, next to the hive, as a segmented control.
New module "Karvan" (self-contained; inspired by karvan-protocol ephemeral rooms):
- Ephemeral/temporary chat rooms held only in RAM, self-destructing on idle
(30 min) / absolute (2 h) TTL — nothing is written to disk.
- Server relay chat (RAM + polling) as the reliable path, plus a WebRTC
data-channel layer (perfect negotiation, HTTP signaling mailbox) for instant
P2P delivery; degrades to the relay if WebRTC is unavailable. Data-channel
only (no camera/mic) so no wrapper permission changes are needed.
- Files: models/karvan_model.js, views/karvan_view.js, client/public/js/karvan.js;
routes /karvan* + karvanMod config + nav entry (network) + karvanTitle i18n (11 langs).
Additive and gated by OASIS_MOBILE / karvanMod; Linux and the UX_OASIS branch untouched.
Mobile-usability experiment grounded in real studies (Hoober 2013,
Bergstrom-Lehtovirta CHI 2011, Parhi 2006 target size, NN/g 2016 visible nav):
- The hive (categories + modules) opens from the bottom bar as a thumb-reachable
bottom-sheet (checkbox-hack, no JS), freeing the top for content.
- Bottom bar reordered: Explore, PM, (+) publish FAB, Search, Inbox; the central
FAB is the distinct, elevated primary action.
- Network/status quick row (Peers, Graphos, Inbox, Settings) inside the sheet.
- Tap targets >=48px. Additive and gated by OASIS_MOBILE (Linux untouched).
- Topbar: single fixed line [logo][Personal][Community][avatar] in one dark
tone (#121212) with gold accents only; the hive hexagons scroll below it.
- Hive: Personal/Community filter the home hive from any page; opening a
category shows its modules as a 2x2 grid of square buttons (a lone odd
button is centered instead of left-aligned).
- Chats: per-message reply (?replyTo=), chat switcher strip and quote banner;
the message rate limit is lifted on mobile.
- Forums: optional subtopics (?topic=) with threaded rendering.
- Bottombar: fixed Inbox - PM - Write - Search - Graphos - Peers.