diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 09aff5c60a1cbd67179663d5a70b5bc7208169fd..f924ffa60340a1e698e657a3fb5da535b95b03c3 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -18,11 +18,11 @@ datetime = "0.6.1"
 sandwich = "2.0.10"
 arrow = "1.2.4"
 # Main Koin version - for core, android deps
-koin = "4.0.0"
-koinTest = "4.0.0"
+koin = "4.1.0-Beta1"
+koinTest = "4.1.0-Beta1"
 gradleBuildConfigPlugin = "5.5.1"
 # Koin version for Compose multiplatform
-koinComposeMultiplatform = "4.0.0"
+koinComposeMultiplatform = "4.1.0-Beta1"
 mosaic = "0.13.0"
 molecule = "2.0.0"
 adaptive = "1.0.1"
@@ -47,7 +47,7 @@ kmpIo = "0.1.5"
 kotlinSemver = "2.0.0"
 temerity = "[0.1.0-dev0z+41180a5,0.1.0]"
 
-kotlinx-dataframe = "0.13.1"
+kotlinx-dataframe = "0.14.2"
 klaxon = "5.6"
 kotest = "6.0.0.M1"
 kotest-datatest = "5.9.1"
diff --git a/temerity/TemerityDemo.ipynb b/temerity/TemerityDemo.ipynb
index 55b1a462c121edb5bb154f47c948aea6d1df8f95..5c539d3c3d1f8ee0e4f5087dca22942865f77de7 100644
--- a/temerity/TemerityDemo.ipynb
+++ b/temerity/TemerityDemo.ipynb
@@ -2,27 +2,101 @@
  "cells": [
   {
    "cell_type": "code",
-   "execution_count": null,
    "metadata": {
-    "collapsed": true
+    "collapsed": true,
+    "ExecuteTime": {
+     "end_time": "2024-11-26T08:55:53.124544800Z",
+     "start_time": "2024-11-26T08:55:52.761207200Z"
+    }
    },
-   "outputs": [],
    "source": [
-    "import edu.ucsc.its.temerity.PlatformClient\n",
+    "// README: Execute this notebook with the temerity.shared.shared.jvmMain source set active ^^^\n",
+    "import edu.ucsc.its.temerity.TemClientConfig\n",
+    "import edu.ucsc.its.temerity.core.Temerity\n",
+    "import io.github.cdimascio.dotenv.Dotenv\n",
     "import kotlinx.coroutines.runBlocking\n",
-    "        \n",
-    "startTemerity()\n",
-    "val client = platformClient {\n",
-    "            serviceEndpoint(\"https://your_organization.yuja.com/services/\")\n",
-    "            serviceToken(\"your_API_token\") }\n",
-    "runBlocking { client.getUsers().forEach { println(it) } }\n",
+    "import org.dotenv.vault.dotenvVault\n",
     "\n",
-    "val returnedDevices = runBlocking { client.getDevices() }\n",
-    "println(\"Returned devices:\")\n",
-    "returnedDevices.forEach { println(it) }\n",
+    "fun TemClientConfig.configureDevNotebookEnvironment(dotenv: Dotenv) {\n",
+    "  serviceUrl = dotenv[\"YUJADEV_API_URL\"]\n",
+    "  serviceToken = dotenv[\"YUJADEV_TOKEN\"]\n",
+    "  optDebugEnabled = true\n",
+    "  supportKtxNotebook = true\n",
+    "}\n",
     "\n",
-    "stopTemerity()"
-   ]
+    "\n",
+    "\n",
+    "//val temerityClient = Temerity {\n",
+    "//  serviceUrl = \"https://your_organization.yuja.com/services/\"\n",
+    "//  serviceToken = \"your_API_token\"\n",
+    "//}\n",
+    "\n",
+    "val temerityClient = Temerity {\n",
+    "  configureDevNotebookEnvironment(dotenvVault())\n",
+    "}\n",
+    "runBlocking { temerityClient.refreshCachedUserRoles().joinToString(\", \").also { println() } }\n",
+    "\n",
+    "val returnedDevices = runBlocking { temerityClient.getDevices() }\n",
+    "println(\"Returned devices: ${ returnedDevices.joinToString(\", \") }\")\n",
+    "\n",
+    "\n",
+    "\n"
+   ],
+   "outputs": [
+    {
+     "ename": "org.jetbrains.kotlinx.jupyter.exceptions.ReplCompilerException",
+     "evalue": "at Cell In[1], line 11, column 3: Unresolved reference: supportKtxNotebook",
+     "output_type": "error",
+     "traceback": [
+      "org.jetbrains.kotlinx.jupyter.exceptions.ReplCompilerException: at Cell In[1], line 11, column 3: Unresolved reference: supportKtxNotebook",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.JupyterCompilerImpl.compileSync(JupyterCompilerImpl.kt:208)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.InternalEvaluatorImpl.eval(InternalEvaluatorImpl.kt:126)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl$execute$1$result$1.invoke(CellExecutorImpl.kt:80)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl$execute$1$result$1.invoke(CellExecutorImpl.kt:78)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.withHost(ReplForJupyterImpl.kt:774)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl.execute-L4Nmkdk(CellExecutorImpl.kt:78)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.execution.CellExecutor$DefaultImpls.execute-L4Nmkdk$default(CellExecutor.kt:13)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.evaluateUserCode-wNURfNM(ReplForJupyterImpl.kt:596)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.evalExImpl(ReplForJupyterImpl.kt:454)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.access$evalExImpl(ReplForJupyterImpl.kt:141)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl$evalEx$1.invoke(ReplForJupyterImpl.kt:447)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl$evalEx$1.invoke(ReplForJupyterImpl.kt:446)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.withEvalContext(ReplForJupyterImpl.kt:427)",
+      "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.evalEx(ReplForJupyterImpl.kt:446)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$processExecuteRequest$1$response$1$1.invoke(IdeCompatibleMessageRequestProcessor.kt:171)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$processExecuteRequest$1$response$1$1.invoke(IdeCompatibleMessageRequestProcessor.kt:170)",
+      "\tat org.jetbrains.kotlinx.jupyter.streams.BlockingSubstitutionEngine.withDataSubstitution(SubstitutionEngine.kt:70)",
+      "\tat org.jetbrains.kotlinx.jupyter.streams.StreamSubstitutionManager.withSubstitutedStreams(StreamSubstitutionManager.kt:118)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.withForkedIn(IdeCompatibleMessageRequestProcessor.kt:347)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.access$withForkedIn(IdeCompatibleMessageRequestProcessor.kt:67)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$evalWithIO$1$1.invoke(IdeCompatibleMessageRequestProcessor.kt:361)",
+      "\tat org.jetbrains.kotlinx.jupyter.streams.BlockingSubstitutionEngine.withDataSubstitution(SubstitutionEngine.kt:70)",
+      "\tat org.jetbrains.kotlinx.jupyter.streams.StreamSubstitutionManager.withSubstitutedStreams(StreamSubstitutionManager.kt:118)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.withForkedErr(IdeCompatibleMessageRequestProcessor.kt:336)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.access$withForkedErr(IdeCompatibleMessageRequestProcessor.kt:67)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$evalWithIO$1.invoke(IdeCompatibleMessageRequestProcessor.kt:360)",
+      "\tat org.jetbrains.kotlinx.jupyter.streams.BlockingSubstitutionEngine.withDataSubstitution(SubstitutionEngine.kt:70)",
+      "\tat org.jetbrains.kotlinx.jupyter.streams.StreamSubstitutionManager.withSubstitutedStreams(StreamSubstitutionManager.kt:118)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.withForkedOut(IdeCompatibleMessageRequestProcessor.kt:328)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.evalWithIO(IdeCompatibleMessageRequestProcessor.kt:359)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$processExecuteRequest$1$response$1.invoke(IdeCompatibleMessageRequestProcessor.kt:170)",
+      "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$processExecuteRequest$1$response$1.invoke(IdeCompatibleMessageRequestProcessor.kt:169)",
+      "\tat org.jetbrains.kotlinx.jupyter.execution.JupyterExecutorImpl$Task.execute(JupyterExecutorImpl.kt:41)",
+      "\tat org.jetbrains.kotlinx.jupyter.execution.JupyterExecutorImpl$executorThread$1.invoke(JupyterExecutorImpl.kt:81)",
+      "\tat org.jetbrains.kotlinx.jupyter.execution.JupyterExecutorImpl$executorThread$1.invoke(JupyterExecutorImpl.kt:79)",
+      "\tat kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)",
+      ""
+     ]
+    }
+   ],
+   "execution_count": 1
+  },
+  {
+   "metadata": {},
+   "cell_type": "code",
+   "source": "",
+   "outputs": [],
+   "execution_count": null
   }
  ],
  "metadata": {
@@ -41,26 +115,9 @@
    "nbconvert_exporter": ""
   },
   "ktnbPluginMetadata": {
+   "projectLibraries": false,
    "projectDependencies": [
-    "Temerity.library.androidTest",
-    "Temerity.library.appleMain",
-    "Temerity.library.appleTest",
-    "Temerity.library.commonMain",
-    "Temerity.library.commonTest",
-    "Temerity.library.iosArm64Main",
-    "Temerity.library.iosArm64Test",
-    "Temerity.library.iosMain",
-    "Temerity.library.iosSimulatorArm64Main",
-    "Temerity.library.iosSimulatorArm64Test",
-    "Temerity.library.iosTest",
-    "Temerity.library.iosX64Main",
-    "Temerity.library.iosX64Test",
-    "Temerity.library.jvmMain",
-    "Temerity.library.jvmTest",
-    "Temerity.library.main",
-    "Temerity.library.nativeMain",
-    "Temerity.library.nativeTest",
-    "Temerity.library.unitTest"
+    "temerity.shared.shared.jvmMain"
    ]
   }
  },
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemClientConfig.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemClientConfig.kt
index cf6d4610dc67f584889f91479dc9f9c1383d33a9..7f43c7a455cffb2dc97d7a5afa605f9056c3a290 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemClientConfig.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemClientConfig.kt
@@ -45,6 +45,7 @@ public class TemClientConfig {
   public var serviceUrl: String? = null
   public var serviceToken: String? = null
   public var optDebugEnabled: Boolean = false
+  public var supportKtxNotebook: Boolean = false
 
   public var expectSuccess: Boolean = true
   public var useWebTimeout: Boolean = false
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/HttpClientFactory.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/HttpClientFactory.kt
index 920110d43bd9c8b441ae4f7cd877502089e27413..309161e04aa5d4fc8448a66f905017529bbf677e 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/HttpClientFactory.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/HttpClientFactory.kt
@@ -67,22 +67,25 @@ internal object HttpClientFactory {
       }
 
       if (config.optDebugEnabled) {
-        if (config.httpClientLoggingBlock == null) {
-          install(Logging) {
-            val kermit = get().get<KermitLogger>()
-            logger = object : KtorLogger {
-              override fun log(message: String) {
-                kermit.d(message)
+        when (config.httpClientLoggingBlock) {
+          null -> {
+            install(Logging) {
+              val kermit = get().get<KermitLogger>()
+              logger = object : KtorLogger {
+                override fun log(message: String) {
+                  kermit.d(message)
+                }
+              }
+              level = LogLevel.ALL
+              sanitizeHeader { headerKey ->
+                headerKey == "authToken"
               }
-            }
-            level = LogLevel.ALL
-            sanitizeHeader { headerKey ->
-              headerKey == "authToken"
             }
           }
-        } else {
-          config.httpClientLoggingBlock?.let {
-            Logging(it)
+          else -> {
+            config.httpClientLoggingBlock?.let {
+              Logging(it)
+            }
           }
         }
       }
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.kt
index ef8b67b094f090c42f5bcab14435443912d32a6e..322862cb26f5553f052746e1f8c6770f4cf54fba 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.kt
@@ -17,6 +17,7 @@
  */
 package edu.ucsc.its.temerity.core
 
+import co.touchlab.kermit.CommonWriter
 import co.touchlab.kermit.Logger
 import co.touchlab.kermit.NoTagFormatter
 import co.touchlab.kermit.Severity
@@ -25,9 +26,9 @@ import co.touchlab.kermit.platformLogWriter
 
 internal object LoggerFactory {
 
-  internal fun createLogger(tag: String?): Logger = Logger(
+  internal fun createLogger(tag: String?, supportKtxNotebook: Boolean): Logger = Logger(
     config = loggerConfigInit(
-      platformLogWriter(NoTagFormatter),
+      if (supportKtxNotebook) CommonWriter(NoTagFormatter) else platformLogWriter(NoTagFormatter),
       minSeverity = Severity.Debug,
     ),
     tag = tag ?: "TemerityLib",
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/Koin.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/Koin.kt
index 3a6cc6c9d3ebaed2576061d324a128eecfa69a33..00a414a01fa210c10bcc3d12e6aaa42830fc014a 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/Koin.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/Koin.kt
@@ -30,8 +30,8 @@ import org.koin.core.parameter.parametersOf
 import org.koin.dsl.module
 
 internal fun loggerModule() = module {
-  factory<co.touchlab.kermit.Logger> { tag ->
-    createLogger(tag.getOrNull())
+  factory<co.touchlab.kermit.Logger> { (tag: String?, config: TemClientConfig) ->
+    createLogger(tag, config.supportKtxNotebook)
   }
 }