Tools: ConstruΓ­ un clon de LWN.net en un solo App.vue de 2292 lΓ­neas porque estaba enojado - Full Analysis

Tools: ConstruΓ­ un clon de LWN.net en un solo App.vue de 2292 lΓ­neas porque estaba enojado - Full Analysis

El origen: pura rabia productiva

Lo que construΓ­

Qmaker-programmer / tuxtimes

🐧 Plataforma de noticias Linux libre, gratis y sin paywalls. Construida con Vue 3 + Firebase. Nació de la rabia contra LWN.net. GPLv2.

🐧 TuxTimes

El lector de noticias Linux que naciΓ³ de la rabia pura y el odio a los paywalls arcaicos

πŸ† TuxTimes vs LWN.net β€” La Comparativa Final

El stack

Los features que LWN no tiene

La parte tΓ©cnica interesante

El sistema de comentarios recursivo

El enrutamiento sin router

El Easter Egg

Los 3 temas

Lo que aprendΓ­

El estado actual

Contribuye

El estado actual

Contribuye LΓ­nea 8 de mi App.vue: AsΓ­ naciΓ³ TuxTimes. Sin reuniΓ³n de planificaciΓ³n. Sin roadmap. Sin arquitectura de microservicios. Solo un desarrollador, un cafΓ©, y una rabia muy especΓ­fica contra LWN.net. LWN es legendario. Sus artΓ­culos sobre el kernel Linux son insustituibles. Pero en 2025: AsΓ­ que dije "suficiente" y abrΓ­ el editor. TuxTimes es una plataforma de noticias sobre Linux y software libre donde cualquiera puede publicar, todo es gratis, y la interfaz no duele usarla. πŸ”— tuxtimes.web.app | * "NaciΓ³ porque estaba enojado con pΓ‘ginas con paywalls arcaicos (te hablo a ti β†’ lwn.net ←)" β€” El autor, comentario en el cΓ³digo, lΓ­nea 8 🐧 Un pingΓΌino rebelde Β· Una app de Vue Β· Un grito de independencia informativa 🐧 LWN lleva desde 1998. TuxTimes lleva un fin de semana. El resultado habla solo. Score: TuxTimes 19/19 πŸ† β€” LWN 3/19 πŸ’€ El feature mΓ‘s dramΓ‘tico del proyecto. Comentarios en Γ‘rbol con depth hasta 5, y un algoritmo que me tomΓ³ mΓ‘s tiempo del que admito: El Algoritmo de Poda de Fantasmasβ„’: Cuando borras un comentario que tiene respuestas: Sin React Router. Sin Vue Router. Solo window.location.hash y orgullo: SΓ­. Si buscas "windows" aparece una pantalla azul de la muerte de Linux. No me arrepiento. Dark, Light, y High Contrast β€” inspirado en el tema de VSCodium. Azul neΓ³n sobre negro total. El usuario se siente hackeando la NASA. Guardados en cookie, no en Firebase. Es preferencia del navegador, no del servidor. 1. Un monolito no siempre es malo Todo vive en App.vue. 2292 lΓ­neas. CSS incluido. ΒΏEs buena arquitectura? No. ΒΏFunciona perfectamente? SΓ­. A veces la velocidad de iteraciΓ³n vale mΓ‘s que la separaciΓ³n de concerns. 2. Firestore cobra por query β€” literalmente Aprendes a optimizar cuando cada .getDocs() de mΓ‘s sale de tu bolsillo. El resultado: cΓ³digo que reutiliza datos locales siempre que puede. 3. El enojo es un motor vΓ‘lido Literalmente la mejor motivaciΓ³n que tuve. No habΓ­a cliente, no habΓ­a deadline, no habΓ­a dinero. Solo la rabia de que LWN no tiene dark mode en 2025 y la certeza de que yo podΓ­a hacerlo mejor. Si eres de la comunidad Linux y quieres una plataforma libre donde publicar lo que aprendes, TuxTimes existe para eso. GPLv2. Fork it. MejΓ³ralo. MΓ‘ndale un PR. O simplemente ΓΊsalo: tuxtimes.web.app 1. Un monolito no siempre es malo Todo vive en App.vue. 2292 lΓ­neas. CSS incluido. ΒΏEs buena arquitectura? No. ΒΏFunciona perfectamente? SΓ­. A veces la velocidad de iteraciΓ³n vale mΓ‘s que la separaciΓ³n de concerns. 2. Firestore cobra por query β€” literalmente Aprendes a optimizar cuando cada .getDocs() de mΓ‘s sale de tu bolsillo. El resultado: cΓ³digo que reutiliza datos locales siempre que puede. 3. El enojo es un motor vΓ‘lido Literalmente la mejor motivaciΓ³n que tuve. No habΓ­a cliente, no habΓ­a deadline, no habΓ­a dinero. Solo la rabia de que LWN no tiene dark mode en 2025 y la certeza de que yo podΓ­a hacerlo mejor. Si eres de la comunidad Linux y quieres una plataforma libre donde publicar lo que aprendes, TuxTimes existe para eso. GPLv2. Fork it. MejΓ³ralo. MΓ‘ndale un PR. O simplemente ΓΊsalo: tuxtimes.web.app β€” Qmaker (Andres / Andresuno), el ΓΊnico loco que hizo esto en un solo App.vue de 2292 lΓ­neas, en un fin de semana, por puracu rabia productiva 🫑🐧 Β‘Prueba TuxTimes ahora mismo! ΒΏQuΓ© opinan de meter 2000 lΓ­neas en un solo archivo? ΒΏEs arte o es un crimen? Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ // y si todo o casi todo en un archivo, y GPLV2 ΒΏPOR QUE? // por que estaba enojado con paginas con paywalls arcaicos // (te hablo a ti -> lwn.net <-) // y si todo o casi todo en un archivo, y GPLV2 ΒΏPOR QUE? // por que estaba enojado con paginas con paywalls arcaicos // (te hablo a ti -> lwn.net <-) // y si todo o casi todo en un archivo, y GPLV2 ΒΏPOR QUE? // por que estaba enojado con paginas con paywalls arcaicos // (te hablo a ti -> lwn.net <-) β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β•šβ•β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β• β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β•šβ•β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β• // RECOLECTOR DE BASURA EN CASCADA REVERSA let currentParentId = currentComment?.parentId; while (currentParentId) { // Si el padre ya era fantasma y no le quedΓ³ nada ΓΊtil... Β‘exterminado! if (!parentHasLiveChildren) { await deleteDoc(parentRef); currentParentId = parentNode.parentId; continue; } break; } // RECOLECTOR DE BASURA EN CASCADA REVERSA let currentParentId = currentComment?.parentId; while (currentParentId) { // Si el padre ya era fantasma y no le quedΓ³ nada ΓΊtil... Β‘exterminado! if (!parentHasLiveChildren) { await deleteDoc(parentRef); currentParentId = parentNode.parentId; continue; } break; } // RECOLECTOR DE BASURA EN CASCADA REVERSA let currentParentId = currentComment?.parentId; while (currentParentId) { // Si el padre ya era fantasma y no le quedΓ³ nada ΓΊtil... Β‘exterminado! if (!parentHasLiveChildren) { await deleteDoc(parentRef); currentParentId = parentNode.parentId; continue; } break; } // Porque en el aΓ±o 2024 seguimos usando el fragmento de la URL // como sistema de rutas. React Router llorando en un rincΓ³n. // Ventaja: sin SSR, sin 404s, sin drama. Solo un # y listo. const HASH_VIEWS = { '#reciente': 'feed', '#configuracion': 'settings', '#favoritos': 'favorites', '#nuevopost': 'new-post', // #post-{ID} β†’ abre ese post directamente (compartible) // #cualquier-otra-cosa β†’ filtra por ese tag }; // Porque en el aΓ±o 2024 seguimos usando el fragmento de la URL // como sistema de rutas. React Router llorando en un rincΓ³n. // Ventaja: sin SSR, sin 404s, sin drama. Solo un # y listo. const HASH_VIEWS = { '#reciente': 'feed', '#configuracion': 'settings', '#favoritos': 'favorites', '#nuevopost': 'new-post', // #post-{ID} β†’ abre ese post directamente (compartible) // #cualquier-otra-cosa β†’ filtra por ese tag }; // Porque en el aΓ±o 2024 seguimos usando el fragmento de la URL // como sistema de rutas. React Router llorando en un rincΓ³n. // Ventaja: sin SSR, sin 404s, sin drama. Solo un # y listo. const HASH_VIEWS = { '#reciente': 'feed', '#configuracion': 'settings', '#favoritos': 'favorites', '#nuevopost': 'new-post', // #post-{ID} β†’ abre ese post directamente (compartible) // #cualquier-otra-cosa β†’ filtra por ese tag }; // EASTER EGG: si el usuario busca "windows" β†’ BSoD de Linux. // (si alguien reporta esto como bug, es que no entiende la cultura) watch(searchQuery, (v) => { showWindowsEgg.value = v.toLowerCase().includes('windows'); }); // EASTER EGG: si el usuario busca "windows" β†’ BSoD de Linux. // (si alguien reporta esto como bug, es que no entiende la cultura) watch(searchQuery, (v) => { showWindowsEgg.value = v.toLowerCase().includes('windows'); }); // EASTER EGG: si el usuario busca "windows" β†’ BSoD de Linux. // (si alguien reporta esto como bug, es que no entiende la cultura) watch(searchQuery, (v) => { showWindowsEgg.value = v.toLowerCase().includes('windows'); }); [data-theme="hc"] { --bg: #000000; --accent: #1aabff; --border: #0078d4; } [data-theme="hc"] .post-card { border-color: #0078d4; box-shadow: 0 0 0 1px #0078d4; } [data-theme="hc"] .post-card:hover { box-shadow: 0 0 16px rgba(26,171,255,.4); } [data-theme="hc"] { --bg: #000000; --accent: #1aabff; --border: #0078d4; } [data-theme="hc"] .post-card { border-color: #0078d4; box-shadow: 0 0 0 1px #0078d4; } [data-theme="hc"] .post-card:hover { box-shadow: 0 0 16px rgba(26,171,255,.4); } [data-theme="hc"] { --bg: #000000; --accent: #1aabff; --border: #0078d4; } [data-theme="hc"] .post-card { border-color: #0078d4; box-shadow: 0 0 0 1px #0078d4; } [data-theme="hc"] .post-card:hover { box-shadow: 0 0 16px rgba(26,171,255,.4); } // Filtra los posts del autor desde el array LOCAL (sin query extra πŸ’Έ) const authorPostsList = posts.value.filter(p => p.authorUid === post.authorUid || p.author === post.author ); // Filtra los posts del autor desde el array LOCAL (sin query extra πŸ’Έ) const authorPostsList = posts.value.filter(p => p.authorUid === post.authorUid || p.author === post.author ); // Filtra los posts del autor desde el array LOCAL (sin query extra πŸ’Έ) const authorPostsList = posts.value.filter(p => p.authorUid === post.authorUid || p.author === post.author ); -weight: 500;">git clone https://github.com/Qmaker-programmer/tuxtimes.-weight: 500;">git cd tuxtimes -weight: 500;">npm -weight: 500;">install cp src/firebase.example.js src/firebase.js -weight: 500;">npm run dev -weight: 500;">git clone https://github.com/Qmaker-programmer/tuxtimes.-weight: 500;">git cd tuxtimes -weight: 500;">npm -weight: 500;">install cp src/firebase.example.js src/firebase.js -weight: 500;">npm run dev -weight: 500;">git clone https://github.com/Qmaker-programmer/tuxtimes.-weight: 500;">git cd tuxtimes -weight: 500;">npm -weight: 500;">install cp src/firebase.example.js src/firebase.js -weight: 500;">npm run dev // Filtra los posts del autor desde el array LOCAL (sin query extra πŸ’Έ) const authorPostsList = posts.value.filter(p => p.authorUid === post.authorUid || p.author === post.author ); // Filtra los posts del autor desde el array LOCAL (sin query extra πŸ’Έ) const authorPostsList = posts.value.filter(p => p.authorUid === post.authorUid || p.author === post.author ); // Filtra los posts del autor desde el array LOCAL (sin query extra πŸ’Έ) const authorPostsList = posts.value.filter(p => p.authorUid === post.authorUid || p.author === post.author ); -weight: 500;">git clone https://github.com/Qmaker-programmer/tuxtimes.-weight: 500;">git cd tuxtimes -weight: 500;">npm -weight: 500;">install cp src/firebase.example.js src/firebase.js -weight: 500;">npm run dev -weight: 500;">git clone https://github.com/Qmaker-programmer/tuxtimes.-weight: 500;">git cd tuxtimes -weight: 500;">npm -weight: 500;">install cp src/firebase.example.js src/firebase.js -weight: 500;">npm run dev -weight: 500;">git clone https://github.com/Qmaker-programmer/tuxtimes.-weight: 500;">git cd tuxtimes -weight: 500;">npm -weight: 500;">install cp src/firebase.example.js src/firebase.js -weight: 500;">npm run dev - πŸ’Έ Paywall de $35/aΓ±o para leer artΓ­culos de la semana - πŸ”’ Solo los editores pueden publicar β€” no hay comunidad - πŸ—žοΈ La interfaz parece un periΓ³dico de 1994 (sin hipΓ©rbole) - πŸ“± Sin dark mode. Sin responsivo decente. Sin filtros modernos. - Vue 3 Composition API β€” porque Options API es 2020 - Firebase Auth β€” Google OAuth sin servidores propios - Firestore β€” NoSQL que cobra por query (asΓ­ aprendes a no hacer queries en bucles) - marked β€” Markdown β†’ HTML, magia negra controlada - Vite β€” porque webpack es un recuerdo doloroso - GPLv2 β€” por coherencia ideolΓ³gica y enojo - Si tiene hijos vivos β†’ soft delete: [Este comentario ha sido eliminado] - Si no tiene hijos β†’ purga fΓ­sica de Firestore - Cascada reversa: sube por el Γ‘rbol eliminando padres fantasmas vacΓ­os - βœ… Auth completa (Google + Email) - βœ… Editor Markdown con preview en tiempo real - βœ… Comentarios en Γ‘rbol con poda de fantasmas - βœ… 3 temas (dark/light/high contrast) - βœ… PaginaciΓ³n de 20 posts con sort - βœ… Perfiles pΓΊblicos de autor - βœ… Sistema de favoritos - βœ… Responsive en 3 breakpoints - βœ… Easter egg de Windows - ⏳ Tests (el futuro nosotros lo resolverΓ‘) - ⏳ PaginaciΓ³n en Firestore (ahora es client-side β€” sΓ­, lo sΓ©) - βœ… Auth completa (Google + Email) - βœ… Editor Markdown con preview en tiempo real - βœ… Comentarios en Γ‘rbol con poda de fantasmas - βœ… 3 temas (dark/light/high contrast) - βœ… PaginaciΓ³n de 20 posts con sort - βœ… Perfiles pΓΊblicos de autor - βœ… Sistema de favoritos - βœ… Responsive en 3 breakpoints - βœ… Easter egg de Windows - ⏳ Tests (el futuro nosotros lo resolverΓ‘) - ⏳ PaginaciΓ³n en Firestore (ahora es client-side β€” sΓ­, lo sΓ©)