Integrate typed SSD server lifecycle

ober

20f86946acd8afdc42b0500c745bd0cc4442e50c

diff --git a/templates/ssd-review-parts/main-server-lifecycle.ss b/templates/ssd-review-parts/main-server-lifecycle.ss
new file mode 100644
index 0000000..ca6791b
--- /dev/null
+++ b/templates/ssd-review-parts/main-server-lifecycle.ss
@@ -0,0 +1,205 @@
+(import (jerboa prelude))
+
+(def fragment
+  '((typed-kotlin-file "com/sfb/ssdreview/MainServerLifecycle.kt"
+      (kotlin-imports (android content Context)
+                      (android content SharedPreferences)
+                      (android content SharedPreferences Editor)
+                      (org json JSONObject))
+      (typed-library (com sfb ssdreview)
+        (export mainServerInitialize mainServerRefreshAsync
+                mainServerOnResume mainServerEndpointLabel
+                mainServerSetEndpoint)
+        (type Editor)
+        (type Exception)
+        (type Int32)
+        (type JSONObject)
+        (type MainActivity)
+        (type SfbAiReportClient)
+        (type SfbServerClient)
+        (type SfbServerInfo)
+        (type SharedPreferences)
+        (extern (mainServerDefaultEndpoint) : String
+          (kotlin-value SFB_DEFAULT_ENDPOINT))
+        (extern (mainServerModePrivate) : Int32
+          (kotlin-value Context MODE_PRIVATE))
+        (extern (mainServerPreferences
+                  (activity : MainActivity)
+                  (name : String)
+                  (mode : Int32)) : SharedPreferences
+          (kotlin-member-call getSharedPreferences))
+        (extern (mainServerPreferenceString
+                  (preferences : SharedPreferences)
+                  (key : String)
+                  (fallback : (Nullable String))) : (Nullable String)
+          (kotlin-member-call getString))
+        (extern (mainServerPreferencesEdit
+                  (preferences : SharedPreferences)) : Editor
+          (kotlin-member-call edit))
+        (extern (mainServerEditorPutString
+                  (editor : Editor)
+                  (key : String)
+                  (value : String)) : Editor
+          (kotlin-member-call putString))
+        (extern (mainServerEditorApply (editor : Editor)) : Unit
+          (kotlin-member-call apply))
+        (extern (mainServerClient
+                  (activity : MainActivity)
+                  (endpoint : String)
+                  (token : String)) : SfbServerClient
+          (kotlin-call SfbServerClient))
+        (extern (mainServerAiClient
+                  (activity : MainActivity)
+                  (server : SfbServerClient)) : SfbAiReportClient
+          (kotlin-call SfbAiReportClient))
+        (extern (mainServerClientSet
+                  (activity : MainActivity)
+                  (client : SfbServerClient)) : Unit
+          (kotlin-member-set serverClient))
+        (extern (mainServerAiClientSet
+                  (activity : MainActivity)
+                  (client : SfbAiReportClient)) : Unit
+          (kotlin-member-set aiReportClient))
+        (extern (mainServerClientRaw
+                  (activity : MainActivity)) : SfbServerClient
+          (kotlin-member-get serverClient))
+        (extern (mainServerClientEndpoint
+                  (client : SfbServerClient)) : String
+          (kotlin-member-call endpointLabel))
+        (extern (mainServerClientSetEndpoint
+                  (client : SfbServerClient)
+                  (endpoint : String)) : Unit
+          (kotlin-member-call setEndpoint))
+        (extern (mainServerClientRefresh
+                  (client : SfbServerClient)) : SfbServerInfo
+          (kotlin-member-call refreshServerEndpoint))
+        (extern (mainServerClientIsLoopback
+                  (client : SfbServerClient)) : Bool
+          (kotlin-member-call endpointIsLoopback))
+        (extern (mainServerRunBackgroundNamed
+                  (name : String)
+                  (action : (-> Unit))) : Unit
+          (kotlin-call runBackgroundNamed))
+        (extern (mainServerLogEvent
+                  (activity : MainActivity)
+                  (name : String)
+                  (fields : JSONObject)) : Unit
+          (kotlin-call mainActivityLogClientEvent))
+        (extern (mainServerExceptionMessage
+                  (error : Exception)) : (Nullable String)
+          (kotlin-member-get message))
+        (def (mainServerNullableText
+               (value : (Nullable String))
+               (fallback : String)) : String
+          (if (nullable-null? value)
+            fallback
+            (let ((text (string-trim (nullable-get value))))
+              (if (string-blank? text) fallback text))))
+        (def (mainServerRemotePreferences
+               (activity : MainActivity)) : SharedPreferences
+          (mainServerPreferences
+            activity
+            "ssd_review_remote"
+            (mainServerModePrivate)))
+        (def (mainServerStoredEndpoint
+               (preferences : SharedPreferences)) : String
+          (mainServerNullableText
+            (mainServerPreferenceString
+              preferences
+              "base_url"
+              (nullable-none String))
+            (mainServerDefaultEndpoint)))
+        (def (mainServerStoredToken
+               (preferences : SharedPreferences)) : String
+          (mainServerNullableText
+            (mainServerPreferenceString
+              preferences
+              "bearer_token"
+              (nullable-none String))
+            ""))
+        (def (mainServerStoreEndpoint
+               (activity : MainActivity)
+               (endpoint : String)) : Unit
+          (mainServerEditorApply
+            (mainServerEditorPutString
+              (mainServerPreferencesEdit
+                (mainServerRemotePreferences activity))
+              "base_url"
+              endpoint)))
+        (def (mainServerEndpointLabel
+               (activity : MainActivity)) : String
+          (mainServerClientEndpoint (mainServerClientRaw activity)))
+        (def (mainServerSetEndpoint
+               (activity : MainActivity)
+               (endpoint : String)) : Unit
+          (begin
+            (mainServerClientSetEndpoint
+              (mainServerClientRaw activity)
+              endpoint)
+            (mainServerStoreEndpoint
+              activity
+              (mainServerEndpointLabel activity))))
+        (def (mainServerInitialize
+               (activity : MainActivity)) : Unit
+          (let ((preferences (mainServerRemotePreferences activity)))
+            (let ((server
+                    (mainServerClient
+                      activity
+                      (mainServerStoredEndpoint preferences)
+                      (mainServerStoredToken preferences))))
+              (begin
+                (mainServerClientSet activity server)
+                (mainServerAiClientSet
+                  activity
+                  (mainServerAiClient activity server))))))
+        (def (mainServerRefreshSucceededFields
+               (reason : String)
+               (endpoint : String)) : JSONObject
+          (let ((fields (json-object-empty)))
+            (begin
+              (json-object-put-string! fields "reason" reason)
+              (json-object-put-string! fields "endpoint" endpoint)
+              fields)))
+        (def (mainServerRefreshFailedFields
+               (reason : String)
+               (error : Exception)) : JSONObject
+          (let ((fields (json-object-empty)))
+            (begin
+              (json-object-put-string! fields "reason" reason)
+              (json-object-put-string!
+                fields
+                "error"
+                (mainServerNullableText
+                  (mainServerExceptionMessage error)
+                  "unknown"))
+              fields)))
+        (def (mainServerRefreshAsync
+               (activity : MainActivity)
+               (reason : String)
+               (allowLoopback : Bool)) : Unit
+          (mainServerRunBackgroundNamed
+            "sfb-server-refresh"
+            (lambda ()
+              (try
+                (let ((client (mainServerClientRaw activity)))
+                  (begin
+                    (mainServerClientRefresh client)
+                    (let ((endpoint (mainServerClientEndpoint client)))
+                      (begin
+                        (if (or allowLoopback
+                                (not (mainServerClientIsLoopback client)))
+                          (mainServerStoreEndpoint activity endpoint)
+                          (begin))
+                        (mainServerLogEvent
+                          activity
+                          "server-endpoint-refreshed"
+                          (mainServerRefreshSucceededFields
+                            reason
+                            endpoint))))))
+                (catch (error : Exception)
+                  (mainServerLogEvent
+                    activity
+                    "server-endpoint-refresh-failed"
+                    (mainServerRefreshFailedFields reason error)))))))
+        (def (mainServerOnResume (activity : MainActivity)) : Unit
+          (mainServerRefreshAsync activity "resume" #f))))))
diff --git a/templates/ssd-review-parts/server-ai.ss b/templates/ssd-review-parts/server-ai.ss
index 669e1fe..475200d 100644
--- a/templates/ssd-review-parts/server-ai.ss
+++ b/templates/ssd-review-parts/server-ai.ss
@@ -5,4 +5,5 @@
     (include-fragment "server-endpoint-cache.ss")
     (include-fragment "server-http.ss")
     (include-fragment "server-client.ss")
-    (include-fragment "ai-report.ss")))
+    (include-fragment "ai-report.ss")
+    (include-fragment "main-server-lifecycle.ss")))