For now? I'm experimenting on an old concept.
1

Configure Feed

Select the types of activity you want to include in your feed.

Outline new module split


Signed-off-by: MLC Bloeiman <mar@strawmelonjuice.com>

MLC Bloeiman (Jul 9, 2026, 9:49 PM +0200) c32fb231 a9365472

+84 -302
+19 -297
web/src/lumina_client.gleam
··· 1 - //// Lumina > Client 1 + //// Lumina > Web-end 2 2 //// Main entry point for Lumina's web frontend. This module contains all side-effects, the update function. Lustre initialisation and more. 3 3 //// 4 4 //// It'll also contain one of the two update() function implementations, the websocket one. Since the Gleam backend ··· 22 22 23 23 // Imports ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 24 25 - import gleam/option.{None} 26 - import gleam/result 27 - import gleam/string 28 - import lumina_client/model_type.{ 29 - type Model, type Msg as Message, type Route, Model, 30 - } 25 + import lumina_client/message.{type Message} 26 + import lumina_client/model_type 31 27 import lumina_client/view 32 - import lustre 33 28 import lustre/effect 34 29 import off_topic 35 30 36 31 // Entrypoints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 37 - /// Entry point for mainly the Rust backend, which doesn't use server-components and does a server-client based 38 - /// approach instead. 39 - pub fn main() -> Result(lustre.Runtime(Message), lustre.Error) { 40 - // This module was a mess, and the lustre_websocket package is outdated. 41 - // Good reason for me to throw it all out amidst a refactor! 42 - // - Mar 43 - // let assert Ok(_) = lustre.start(app(api_based_updates), "#app", Nil) 44 - todo as "The api wrapper should kick in here." 45 - } 46 32 47 - /// Main entry of the lumina_client, meant to be consumed as a server component 48 - pub fn app() { 49 - off_topic.component( 50 - init:, 51 - update:, 52 - view: view.view, 53 - subscriptions:, 54 - options: [], 55 - ) 33 + pub fn main() { 34 + panic as "Currently, running the frontend as a js bundle is unsupported, as focus is on server components." 56 35 } 57 36 58 - // Common functionality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 59 - 60 - /// Init function 61 - fn init(_) -> #(Model, effect.Effect(Message)) { 62 - let initial_route = model_type.Landing 63 - #( 64 - Model(page: initial_route, user: None, token: None, status: Ok(Nil)), 65 - effect.none(), 66 - ) 67 - |> echo as "Init output" 37 + /// Entrypoint to interface directly, usable in server components 38 + pub fn app( 39 + update: fn(model_type.Model, Message) -> 40 + #(model_type.Model, effect.Effect(Message)), 41 + ) { 42 + off_topic.application(init:, update:, view: view.view, subscriptions:) 68 43 } 69 44 70 - // Event handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 71 - 72 - /// Subscriptions 73 - fn subscriptions(_model: Model) -> off_topic.Subscription(Message) { 74 - off_topic.batch([]) 45 + fn subscriptions(_model: model_type.Model) -> off_topic.Subscription(Message) { 46 + // for now 47 + off_topic.none() 75 48 } 76 49 77 - /// Update function used mainly with the Rust backend 78 - fn update(model: Model, message: Message) -> #(Model, effect.Effect(Message)) { 79 - echo message as "Received message" 80 - case message { 81 - model_type.UserNavigatedToLoginPage -> #( 82 - Model( 83 - ..model, 84 - page: model_type.Login( 85 - fields: model_type.LoginFields("", ""), 86 - success: None, 87 - ), 88 - ), 89 - effect.none(), 90 - ) 91 - model_type.UserNavigatedToRegisterPage -> #( 92 - Model( 93 - ..model, 94 - page: model_type.Register( 95 - fields: model_type.RegisterPageFields("", "", "", ""), 96 - ready: None, 97 - ), 98 - ), 99 - effect.none(), 100 - ) 101 - model_type.UserNavigatedToLandingPage -> #( 102 - Model(..model, page: model_type.Landing), 103 - effect.none(), 104 - ) 105 - model_type.UserUpdatedControlledEmailField(new_email) -> { 106 - case model.page { 107 - model_type.Register(fields, ready) -> #( 108 - Model( 109 - ..model, 110 - page: model_type.Register( 111 - fields: model_type.RegisterPageFields( 112 - ..fields, 113 - emailfield: new_email, 114 - ), 115 - ready:, 116 - ), 117 - ), 118 - { 119 - // This block emits an effect to send RegisterPrecheck message to the server 120 - todo as "RegisterPrecheck( 121 - fields.emailfield, 122 - fields.usernamefield, 123 - fields.passwordfield, 124 - )" 125 - }, 126 - ) 127 - model_type.Login(fields, _) -> #( 128 - Model( 129 - ..model, 130 - page: model_type.Login( 131 - fields: model_type.LoginFields(..fields, emailfield: new_email), 132 - success: None, 133 - ), 134 - ), 135 - effect.none(), 136 - ) 137 - _ -> #(model, effect.none()) 138 - } 139 - } 140 - model_type.UserUpdatedControlledPasswordField(new_password) -> { 141 - case model.page { 142 - model_type.Register(fields, ready) -> #( 143 - Model( 144 - ..model, 145 - page: model_type.Register( 146 - model_type.RegisterPageFields( 147 - ..fields, 148 - passwordfield: new_password, 149 - ), 150 - ready:, 151 - ), 152 - ), 153 - { 154 - // This block emits an effect to send RegisterPrecheck message to the server 155 - todo as "RegisterPrecheck( 156 - fields.emailfield, 157 - fields.usernamefield, 158 - fields.passwordfield, 159 - )" 160 - }, 161 - ) 162 - model_type.Login(fields, _success) -> { 163 - let username_email = case string.starts_with(fields.emailfield, "@") { 164 - True -> string.drop_start(fields.emailfield, 1) 165 - False -> fields.emailfield 166 - } 167 - let new_username_email = case string.contains(username_email, "@") { 168 - True -> { 169 - // Is an email, what now! 170 - username_email 171 - } 172 - False -> { 173 - string.trim(username_email) 174 - |> string.replace(" ", "") 175 - |> string.lowercase() 176 - |> string.replace("@", "") 177 - |> string.replace(".", "") 178 - } 179 - } 180 - #( 181 - Model( 182 - ..model, 183 - page: model_type.Login( 184 - fields: model_type.LoginFields( 185 - passwordfield: new_password, 186 - emailfield: new_username_email, 187 - ), 188 - success: None, 189 - ), 190 - ), 191 - effect.none(), 192 - ) 193 - } 194 - _ -> #(model, effect.none()) 195 - } 196 - } 197 - model_type.UserUpdatedControlledPasswordConfirmField( 198 - new_password_confirmation, 199 - ) -> { 200 - case model.page { 201 - model_type.Register(fields, ready) -> #( 202 - Model( 203 - ..model, 204 - page: model_type.Register( 205 - fields: model_type.RegisterPageFields( 206 - ..fields, 207 - passwordconfirmfield: new_password_confirmation, 208 - ), 209 - ready:, 210 - ), 211 - ), 212 - { 213 - // This block emits an effect to send RegisterPrecheck message to the server 214 - todo as "RegisterPrecheck( 215 - fields.emailfield, 216 - fields.usernamefield, 217 - fields.passwordfield, 218 - )" 219 - }, 220 - ) 221 - _ -> #(model, effect.none()) 222 - } 223 - } 224 - model_type.UserUpdatedControlledUsernameField(new_username) -> { 225 - case model.page { 226 - model_type.Register(fields, ready) -> #( 227 - Model( 228 - ..model, 229 - page: model_type.Register( 230 - fields: model_type.RegisterPageFields(..fields, usernamefield: { 231 - case string.starts_with(new_username, "@") { 232 - True -> string.drop_start(new_username, 1) 233 - False -> new_username 234 - } 235 - |> string.trim() 236 - |> string.replace(" ", "") 237 - |> string.lowercase() 238 - |> string.replace("@", "") 239 - |> string.replace(".", "") 240 - }), 241 - ready:, 242 - ), 243 - ), 244 - { 245 - todo as "RegisterPrecheck( 246 - fields.emailfield, 247 - fields.usernamefield, 248 - fields.passwordfield, 249 - )" 250 - }, 251 - ) 252 - _ -> #(model, effect.none()) 253 - } 254 - } 255 - model_type.EmailFieldLostFocus -> { 256 - // This handles the login username/email field value once the user seems to be done typing. 257 - let assert model_type.Login(fields, _success) = model.page 258 - let value = case string.starts_with(fields.emailfield, "@") { 259 - True -> string.drop_start(fields.emailfield, 1) 260 - False -> fields.emailfield 261 - } 262 - let new_value = case string.contains(value, "@") { 263 - True -> { 264 - // Is an email, what now! 265 - value 266 - } 267 - False -> { 268 - string.trim(value) 269 - |> string.replace(" ", "") 270 - |> string.lowercase() 271 - |> string.replace("@", "") 272 - |> string.replace(".", "") 273 - } 274 - } 275 - #( 276 - Model( 277 - ..model, 278 - page: model_type.Login( 279 - fields: model_type.LoginFields(..fields, emailfield: new_value), 280 - success: None, 281 - ), 282 - ), 283 - effect.none(), 284 - ) 285 - } 286 - model_type.UserClickedLogout -> todo as "session should be destroyed here" 287 - model_type.UserSubmittedLogin(_) -> { 288 - // let assert model_type.Login(fields, _) = model.page 289 - todo as "LoginAuthenticationRequest( 290 - fields.emailfield, 291 - fields.passwordfield, 292 - )" 293 - } 294 - model_type.UserSubmittedSignup(_) -> { 295 - let assert model_type.Register(fields, ready) = model.page 50 + // Common functionality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 296 51 297 - case 298 - { 299 - { ready |> option.is_some() } 300 - && { ready |> option.unwrap(Error("")) |> result.is_ok() } 301 - && { fields.passwordfield == fields.passwordconfirmfield } 302 - } 303 - { 304 - True -> { 305 - // console.log("Submitting signup form") 306 - let effect = 307 - todo as "RegisterRequest( 308 - fields.emailfield, 309 - fields.usernamefield, 310 - fields.passwordfield, 311 - )" 312 - #(model, effect) 313 - } 314 - False -> { 315 - // console.error("Form not ready to submit") 316 - #(model, effect.none()) 317 - } 318 - } 319 - } 320 - model_type.UserSwitchedTimeLineTo(tid) -> todo 321 - 322 - model_type.LoadMorePosts(timeline_name) -> todo 323 - 324 - model_type.UserClosedModal -> todo 325 - 326 - model_type.StartDraggingModalBox(x, y) -> todo 327 - 328 - model_type.MoveModalBoxTo(x, y) -> todo 329 - 330 - model_type.UpdateLastRefreshRequestTime(_) -> todo 331 - model_type.ModemChangePage(_) -> todo 332 - model_type.SetModal(_) -> todo 333 - } 52 + /// Init function 53 + /// Inherits effects, because these depend on where the init is ran from. 54 + fn init(_) -> #(model_type.Model, effect.Effect(Message)) { 55 + todo 334 56 }
+20
web/src/lumina_client/message.gleam
··· 1 + //// Lumina > Web-end > Messages 2 + 3 + // Lumina/Peonies 4 + // Copyright (C) 2018-2026 MLC 'Strawmelonjuice' Bloeiman and contributors. 5 + // 6 + // This software is licensed under the European Union Public Licence (EUPL) v1.2. 7 + // You may not use this work except in compliance with the Licence. 8 + // You may obtain a copy of the Licence at: https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 9 + // 10 + // AI TRAINING NOTICE: Rights for TDM and AI training are EXPRESSLY RESERVED 11 + // under Art 4(3) Dir 2019/790. AI training constitutes a Derivative Work. 12 + // See LICENSE file in the repository root for full details. 13 + // 14 + // 15 + // This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND. 16 + // See the Licence for the specific language governing permissions and limitations. 17 + 18 + pub type Message { 19 + OldMessage(String) 20 + }
+17
web/src/lumina_client/model.gleam
··· 1 + //// Lumina > Client > Model 2 + //// Lumina's model is the central source of truth for the client application state. 3 + 4 + // Lumina/Peonies 5 + // Copyright (C) 2018-2026 MLC 'Strawmelonjuice' Bloeiman and contributors. 6 + // 7 + // This software is licensed under the European Union Public Licence (EUPL) v1.2. 8 + // You may not use this work except in compliance with the Licence. 9 + // You may obtain a copy of the Licence at: https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 10 + // 11 + // AI TRAINING NOTICE: Rights for TDM and AI training are EXPRESSLY RESERVED 12 + // under Art 4(3) Dir 2019/790. AI training constitutes a Derivative Work. 13 + // See LICENSE file in the repository root for full details. 14 + // 15 + // 16 + // This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND. 17 + // See the Licence for the specific language governing permissions and limitations.
+14
web/src/lumina_client/model_type.gleam
··· 1 1 //// Lumina > Client > Model 2 2 //// Lumina's model is the central source of truth for the client application state. 3 + //// 4 + //// This module is to be replaced by the newer and more targeted model.gleam module. 3 5 4 6 // Lumina/Peonies 5 7 // Copyright (C) 2018-2026 MLC 'Strawmelonjuice' Bloeiman and contributors. ··· 15 17 // 16 18 // This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND. 17 19 // See the Licence for the specific language governing permissions and limitations. 20 + pub fn from_itr2_message( 21 + from: element.Element(Msg), 22 + ) -> element.Element(message.Message) { 23 + element.map(from, fn(original_message: Msg) -> message.Message { 24 + case original_message { 25 + _ -> message.OldMessage(string.inspect(original_message)) 26 + } 27 + }) 28 + } 18 29 19 30 import gleam/dict.{type Dict} 20 31 import gleam/dynamic/decode 21 32 import gleam/json 22 33 import gleam/list 23 34 import gleam/option.{type Option, None, Some} 35 + import gleam/string 24 36 import gleam/uri.{type Uri} 37 + import lumina_client/message 38 + import lustre/element 25 39 26 40 pub type Msg { 27 41 UpdateLastRefreshRequestTime(Int)
+14 -5
web/src/lumina_client/view.gleam
··· 24 24 import lumina_client/helpers.{ 25 25 get_color_scheme, login_view_checker, model_local_storage_key, 26 26 } 27 + import lumina_client/message 27 28 import lumina_client/model_type.{ 28 29 type Model, type Msg, HomeTimeline, Landing, Licence, Login, NotFound, 29 30 Register, UserNavigatedToLandingPage, UserNavigatedToLoginPage, ··· 39 40 import lustre/element/html 40 41 import lustre/event 41 42 42 - pub fn view(model: Model) -> Element(model_type.Msg) { 43 + pub fn view(model: Model) -> Element(message.Message) { 43 44 case model.page { 44 - Landing -> view_landing() 45 - Register(..) -> view_register(model) 46 - Login(..) -> view_login(model) 47 - HomeTimeline(..) -> view_homepage(model) 45 + Landing -> 46 + view_landing() 47 + |> model_type.from_itr2_message 48 + Register(..) -> 49 + view_register(model) 50 + |> model_type.from_itr2_message 51 + Login(..) -> 52 + view_login(model) 53 + |> model_type.from_itr2_message 54 + HomeTimeline(..) -> 55 + view_homepage(model) 56 + |> model_type.from_itr2_message 48 57 NotFound(uri:) -> todo as "No 404 page yet." 49 58 Licence -> 50 59 todo as "Licence should be shown by the client if it's not shown by the server."