From bd9e475f380b653a42163ec9062044e15157a0b5 Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Fri, 16 May 2025 20:49:06 +0200
Subject: [PATCH 1/7] Fixes for LSP running & Improving the install locations

---
 app/build.gradle.kts                 |  3 +-
 app/src/processing/app/Base.java     | 12 ------
 app/src/processing/app/Processing.kt | 64 ++++++++++++++++++++++++++--
 3 files changed, 62 insertions(+), 17 deletions(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c40365758..ef951c879 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -419,7 +419,6 @@ tasks.register<Copy>("renameWindres") {
 }
 tasks.register("includeProcessingResources"){
     dependsOn(
-        "includeJdk",
         "includeCore",
         "includeJavaMode",
         "includeSharedAssets",
@@ -428,6 +427,7 @@ tasks.register("includeProcessingResources"){
         "includeJavaModeResources",
         "renameWindres"
     )
+    mustRunAfter("includeJdk")
     finalizedBy("signResources")
 }
 
@@ -534,6 +534,7 @@ afterEvaluate {
         dependsOn("includeProcessingResources")
     }
     tasks.named("createDistributable").configure {
+        dependsOn("includeJdk")
         finalizedBy("setExecutablePermissions")
     }
 }
diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index b5aa599b9..4d57135c9 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -166,18 +166,6 @@ static public void main(final String[] args) {
   static private void createAndShowGUI(String[] args) {
     // these times are fairly negligible relative to Base.<init>
 //    long t1 = System.currentTimeMillis();
-    var preferences = java.util.prefs.Preferences.userRoot().node("org/processing/app");
-    var installLocations = new ArrayList<>(List.of(preferences.get("installLocations", "").split(",")));
-    var installLocation = System.getProperty("user.dir") + "^" + Base.getVersionName();
-
-    // Check if the installLocation is already in the list
-    if (!installLocations.contains(installLocation)) {
-      // Add the installLocation to the list
-      installLocations.add(installLocation);
-
-      // Save the updated list back to preferences
-      preferences.put("installLocations", String.join(",", installLocations));
-    }
     // TODO: Cleanup old locations if no longer installed
     // TODO: Cleanup old locations if current version is installed in the same location
 
diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt
index 11555edf5..751dd361f 100644
--- a/app/src/processing/app/Processing.kt
+++ b/app/src/processing/app/Processing.kt
@@ -11,6 +11,9 @@ import com.github.ajalt.clikt.parameters.options.flag
 import com.github.ajalt.clikt.parameters.options.help
 import com.github.ajalt.clikt.parameters.options.option
 import processing.app.ui.Start
+import java.io.File
+import java.util.prefs.Preferences
+import kotlin.concurrent.thread
 
 class Processing: SuspendingCliktCommand("processing"){
     val version by option("-v","--version")
@@ -29,6 +32,11 @@ class Processing: SuspendingCliktCommand("processing"){
             return
         }
 
+        thread {
+            // Update the install locations in preferences
+            updateInstallLocations()
+        }
+
         val subcommand = currentContext.invokedSubcommand
         if (subcommand == null) {
             Start.main(sketches.toTypedArray())
@@ -49,10 +57,13 @@ class LSP: SuspendingCliktCommand("lsp"){
     override fun help(context: Context) = "Start the Processing Language Server"
     override suspend fun run(){
         try {
+            // run in headless mode
+            System.setProperty("java.awt.headless", "true")
+
             // Indirect invocation since app does not depend on java mode
             Class.forName("processing.mode.java.lsp.PdeLanguageServer")
                 .getMethod("main", Array<String>::class.java)
-                .invoke(null, *arrayOf<Any>(emptyList<String>()))
+                .invoke(null, arrayOf<String>())
         } catch (e: Exception) {
             throw InternalError("Failed to invoke main method", e)
         }
@@ -76,9 +87,8 @@ class LegacyCLI(val args: Array<String>): SuspendingCliktCommand( "cli"){
     override suspend fun run(){
         val cliArgs = args.filter { it != "cli" }
         try {
-            if(build){
-                System.setProperty("java.awt.headless", "true")
-            }
+            System.setProperty("java.awt.headless", "true")
+
             // Indirect invocation since app does not depend on java mode
             Class.forName("processing.mode.java.Commander")
                 .getMethod("main", Array<String>::class.java)
@@ -87,4 +97,50 @@ class LegacyCLI(val args: Array<String>): SuspendingCliktCommand( "cli"){
             throw InternalError("Failed to invoke main method", e)
         }
     }
+}
+
+fun updateInstallLocations(){
+    val preferences = Preferences.userRoot().node("org/processing/app")
+    val installLocations = preferences.get("installLocations", "")
+        .split(",")
+        .dropLastWhile { it.isEmpty() }
+        .filter { install ->
+            try{
+                val (path, version) = install.split("^")
+                val file = File(path)
+                if(!file.exists() || file.isDirectory){
+                    return@filter false
+                }
+                // call the path to check if it is a valid install location
+                val process = ProcessBuilder(path, "--version")
+                    .redirectErrorStream(true)
+                    .start()
+                val exitCode = process.waitFor()
+                if(exitCode != 0){
+                    return@filter false
+                }
+                val output = process.inputStream.bufferedReader().readText()
+                return@filter output.contains(version)
+            } catch (e: Exception){
+                false
+            }
+        }
+        .toMutableList()
+    val command = ProcessHandle.current().info().command()
+    if(command.isEmpty) {
+        return
+    }
+    val installLocation = "${command.get()}^${Base.getVersionName()}"
+
+
+    // Check if the installLocation is already in the list
+    if (installLocations.contains(installLocation)) {
+        return
+    }
+
+    // Add the installLocation to the list
+    installLocations.add(installLocation)
+
+    // Save the updated list back to preferences
+    preferences.put("installLocations", java.lang.String.join(",", installLocations))
 }
\ No newline at end of file

From 5b27fc650a57ed195593ee6e3c70f6bf1a6ccf53 Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Tue, 20 May 2025 20:43:50 +0200
Subject: [PATCH 2/7] Create Examples JSON

---
 app/build.gradle.kts                          |  1 +
 app/src/processing/app/Processing.kt          |  4 +-
 .../processing/app/contrib/Contributions.kt   | 95 +++++++++++++++++++
 3 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 app/src/processing/app/contrib/Contributions.kt

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ef951c879..79cb4ad15 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -124,6 +124,7 @@ dependencies {
     testImplementation(libs.junitJupiterParams)
 
     implementation(libs.clikt)
+    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
 }
 
 tasks.test {
diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt
index 751dd361f..798ef4b34 100644
--- a/app/src/processing/app/Processing.kt
+++ b/app/src/processing/app/Processing.kt
@@ -10,6 +10,7 @@ import com.github.ajalt.clikt.parameters.arguments.multiple
 import com.github.ajalt.clikt.parameters.options.flag
 import com.github.ajalt.clikt.parameters.options.help
 import com.github.ajalt.clikt.parameters.options.option
+import processing.app.contrib.Contributions
 import processing.app.ui.Start
 import java.io.File
 import java.util.prefs.Preferences
@@ -48,7 +49,8 @@ suspend fun main(args: Array<String>){
    Processing()
         .subcommands(
             LSP(),
-            LegacyCLI(args)
+            LegacyCLI(args),
+            Contributions()
         )
         .main(args)
 }
diff --git a/app/src/processing/app/contrib/Contributions.kt b/app/src/processing/app/contrib/Contributions.kt
new file mode 100644
index 000000000..0c1948d84
--- /dev/null
+++ b/app/src/processing/app/contrib/Contributions.kt
@@ -0,0 +1,95 @@
+package processing.app.contrib
+
+import com.github.ajalt.clikt.command.SuspendingCliktCommand
+import com.github.ajalt.clikt.core.Context
+import com.github.ajalt.clikt.core.subcommands
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+import processing.app.Base
+import java.io.File
+import kotlinx.serialization.json.*
+
+
+class Contributions: SuspendingCliktCommand(){
+    override fun help(context: Context) = "Manage Processing contributions"
+    override suspend fun run() {
+        System.setProperty("java.awt.headless", "true")
+    }
+    init {
+        subcommands(Examples())
+    }
+
+    class Examples: SuspendingCliktCommand("examples") {
+        override fun help(context: Context) = "Manage Processing examples"
+        override suspend fun run() {
+        }
+        init {
+            subcommands(ExamplesList())
+        }
+    }
+
+    class ExamplesList: SuspendingCliktCommand("list") {
+        @Serializable
+        data class Sketch(
+            val type: String = "sketch",
+            val name: String,
+            val path: String,
+            val mode: String = "java",
+        )
+
+        @Serializable
+        data class Folder(
+            val type: String = "folder",
+            val name: String,
+            val path: String,
+            val mode: String = "java",
+            val children: List<Folder> = emptyList(),
+            val sketches: List<Sketch> = emptyList()
+        )
+
+        val serializer = Json{
+            prettyPrint = true
+        }
+
+        override fun help(context: Context) = "List all examples"
+        override suspend fun run() {
+
+            val examplesFolder = Base.getSketchbookExamplesFolder()
+
+            // TODO: Decouple modes listing from `Base` class, defaulting to Java mode for now
+            val resourcesDir = System.getProperty("compose.application.resources.dir")
+            val javaMode = "$resourcesDir/modes/java"
+            val javaModeExamples = "$javaMode/examples"
+
+            val javaExamples = getExamples(File(javaModeExamples))
+
+            val json = serializer.encodeToString(listOf(javaExamples))
+            println(json)
+
+            // Build-in examples for each mode
+            // Get examples for core libraries
+
+            // Examples downloaded in the sketchbook
+            // Library contributions
+            // Mode examples
+        }
+
+        suspend fun getExamples(file: File): Folder {
+            val name = file.name
+            val (sketchesFolders, childrenFolders) = file.listFiles().partition { isExampleFolder(it) }
+
+            val children = childrenFolders.map { getExamples(it) }
+            val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) }
+            return Folder(
+                name = name,
+                path = file.absolutePath,
+                children = children,
+                sketches = sketches
+            )
+        }
+        fun isExampleFolder(file: File): Boolean {
+            return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") }
+        }
+    }
+}
+

From b3fb5582065cfdab2530b35b4a8d80dea23a325e Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Thu, 22 May 2025 08:36:50 +0200
Subject: [PATCH 3/7] Moved into api folder + listing Sketchbook

---
 app/src/processing/app/Processing.kt          |  2 +-
 .../app/{contrib => api}/Contributions.kt     | 28 ++++---------------
 app/src/processing/app/api/Sketch.kt          | 24 ++++++++++++++++
 app/src/processing/app/api/Sketchbook.kt      | 25 +++++++++++++++++
 4 files changed, 56 insertions(+), 23 deletions(-)
 rename app/src/processing/app/{contrib => api}/Contributions.kt (71%)
 create mode 100644 app/src/processing/app/api/Sketch.kt
 create mode 100644 app/src/processing/app/api/Sketchbook.kt

diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt
index 798ef4b34..b72b59af6 100644
--- a/app/src/processing/app/Processing.kt
+++ b/app/src/processing/app/Processing.kt
@@ -10,7 +10,7 @@ import com.github.ajalt.clikt.parameters.arguments.multiple
 import com.github.ajalt.clikt.parameters.options.flag
 import com.github.ajalt.clikt.parameters.options.help
 import com.github.ajalt.clikt.parameters.options.option
-import processing.app.contrib.Contributions
+import processing.app.api.Contributions
 import processing.app.ui.Start
 import java.io.File
 import java.util.prefs.Preferences
diff --git a/app/src/processing/app/contrib/Contributions.kt b/app/src/processing/app/api/Contributions.kt
similarity index 71%
rename from app/src/processing/app/contrib/Contributions.kt
rename to app/src/processing/app/api/Contributions.kt
index 0c1948d84..070874c13 100644
--- a/app/src/processing/app/contrib/Contributions.kt
+++ b/app/src/processing/app/api/Contributions.kt
@@ -1,14 +1,14 @@
-package processing.app.contrib
+package processing.app.api
 
 import com.github.ajalt.clikt.command.SuspendingCliktCommand
 import com.github.ajalt.clikt.core.Context
 import com.github.ajalt.clikt.core.subcommands
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
 import processing.app.Base
+import processing.app.api.Sketch.Companion.getSketches
 import java.io.File
-import kotlinx.serialization.json.*
-
 
 class Contributions: SuspendingCliktCommand(){
     override fun help(context: Context) = "Manage Processing contributions"
@@ -47,7 +47,7 @@ class Contributions: SuspendingCliktCommand(){
             val sketches: List<Sketch> = emptyList()
         )
 
-        val serializer = Json{
+        val serializer = Json {
             prettyPrint = true
         }
 
@@ -61,7 +61,7 @@ class Contributions: SuspendingCliktCommand(){
             val javaMode = "$resourcesDir/modes/java"
             val javaModeExamples = "$javaMode/examples"
 
-            val javaExamples = getExamples(File(javaModeExamples))
+            val javaExamples = getSketches(File(javaModeExamples))
 
             val json = serializer.encodeToString(listOf(javaExamples))
             println(json)
@@ -74,22 +74,6 @@ class Contributions: SuspendingCliktCommand(){
             // Mode examples
         }
 
-        suspend fun getExamples(file: File): Folder {
-            val name = file.name
-            val (sketchesFolders, childrenFolders) = file.listFiles().partition { isExampleFolder(it) }
 
-            val children = childrenFolders.map { getExamples(it) }
-            val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) }
-            return Folder(
-                name = name,
-                path = file.absolutePath,
-                children = children,
-                sketches = sketches
-            )
-        }
-        fun isExampleFolder(file: File): Boolean {
-            return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") }
-        }
     }
-}
-
+}
\ No newline at end of file
diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt
new file mode 100644
index 000000000..6943a7f35
--- /dev/null
+++ b/app/src/processing/app/api/Sketch.kt
@@ -0,0 +1,24 @@
+package processing.app.api
+
+import java.io.File
+
+class Sketch {
+    companion object{
+        fun getSketches(file: File): Contributions.ExamplesList.Folder {
+            val name = file.name
+            val (sketchesFolders, childrenFolders) = file.listFiles().partition { isSketchFolder(it) }
+
+            val children = childrenFolders.map { getSketches(it) }
+            val sketches = sketchesFolders.map { Contributions.ExamplesList.Sketch(name = it.name, path = it.absolutePath) }
+            return Contributions.ExamplesList.Folder(
+                name = name,
+                path = file.absolutePath,
+                children = children,
+                sketches = sketches
+            )
+        }
+        fun isSketchFolder(file: File): Boolean {
+            return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") }
+        }
+    }
+}
diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt
new file mode 100644
index 000000000..c7bdd7623
--- /dev/null
+++ b/app/src/processing/app/api/Sketchbook.kt
@@ -0,0 +1,25 @@
+package processing.app.api
+
+import com.github.ajalt.clikt.command.SuspendingCliktCommand
+import com.github.ajalt.clikt.core.Context
+import com.github.ajalt.clikt.core.subcommands
+import processing.app.Base
+import processing.app.api.Sketch.Companion.getSketches
+
+class Sketchbook: SuspendingCliktCommand() {
+    override fun help(context: Context) = "Manage the sketchbook"
+    override suspend fun run() {
+        System.setProperty("java.awt.headless", "true")
+    }
+    init {
+        subcommands(SketchbookList())
+    }
+    class SketchbookList: SuspendingCliktCommand("list") {
+        override fun help(context: Context) = "List all sketches"
+        override suspend fun run() {
+            val sketchbookFolder = Base.getSketchbookFolder()
+
+            val sketches = getSketches(sketchbookFolder)
+        }
+    }
+}
\ No newline at end of file

From a863cff3ca8650088fc09d9ffe3c5096b96ca163 Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Thu, 22 May 2025 09:14:42 +0200
Subject: [PATCH 4/7] support for listing Sketchbook

---
 app/src/processing/app/Processing.kt        |  4 ++-
 app/src/processing/app/api/Contributions.kt | 16 ----------
 app/src/processing/app/api/Sketch.kt        | 34 ++++++++++++++++++---
 app/src/processing/app/api/Sketchbook.kt    | 23 ++++++++++++--
 4 files changed, 52 insertions(+), 25 deletions(-)

diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt
index b72b59af6..bb05a72f3 100644
--- a/app/src/processing/app/Processing.kt
+++ b/app/src/processing/app/Processing.kt
@@ -11,6 +11,7 @@ import com.github.ajalt.clikt.parameters.options.flag
 import com.github.ajalt.clikt.parameters.options.help
 import com.github.ajalt.clikt.parameters.options.option
 import processing.app.api.Contributions
+import processing.app.api.Sketchbook
 import processing.app.ui.Start
 import java.io.File
 import java.util.prefs.Preferences
@@ -50,7 +51,8 @@ suspend fun main(args: Array<String>){
         .subcommands(
             LSP(),
             LegacyCLI(args),
-            Contributions()
+            Contributions(),
+            Sketchbook()
         )
         .main(args)
 }
diff --git a/app/src/processing/app/api/Contributions.kt b/app/src/processing/app/api/Contributions.kt
index 070874c13..80bfa502f 100644
--- a/app/src/processing/app/api/Contributions.kt
+++ b/app/src/processing/app/api/Contributions.kt
@@ -29,23 +29,7 @@ class Contributions: SuspendingCliktCommand(){
     }
 
     class ExamplesList: SuspendingCliktCommand("list") {
-        @Serializable
-        data class Sketch(
-            val type: String = "sketch",
-            val name: String,
-            val path: String,
-            val mode: String = "java",
-        )
 
-        @Serializable
-        data class Folder(
-            val type: String = "folder",
-            val name: String,
-            val path: String,
-            val mode: String = "java",
-            val children: List<Folder> = emptyList(),
-            val sketches: List<Sketch> = emptyList()
-        )
 
         val serializer = Json {
             prettyPrint = true
diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt
index 6943a7f35..a0e740014 100644
--- a/app/src/processing/app/api/Sketch.kt
+++ b/app/src/processing/app/api/Sketch.kt
@@ -1,16 +1,40 @@
 package processing.app.api
 
+import kotlinx.serialization.Serializable
 import java.io.File
 
 class Sketch {
     companion object{
-        fun getSketches(file: File): Contributions.ExamplesList.Folder {
+        @Serializable
+        data class Sketch(
+            val type: String = "sketch",
+            val name: String,
+            val path: String,
+            val mode: String = "java",
+        )
+
+        @Serializable
+        data class Folder(
+            val type: String = "folder",
+            val name: String,
+            val path: String,
+            val mode: String = "java",
+            val children: List<Folder> = emptyList(),
+            val sketches: List<Sketch> = emptyList()
+        )
+
+        fun getSketches(file: File, filter: (File) -> Boolean = { true }): Folder {
             val name = file.name
-            val (sketchesFolders, childrenFolders) = file.listFiles().partition { isSketchFolder(it) }
+            val (sketchesFolders, childrenFolders) = file.listFiles()?.partition { isSketchFolder(it) } ?: return Folder(
+                name = name,
+                path = file.absolutePath,
+                sketches = emptyList(),
+                children = emptyList()
+            )
 
-            val children = childrenFolders.map { getSketches(it) }
-            val sketches = sketchesFolders.map { Contributions.ExamplesList.Sketch(name = it.name, path = it.absolutePath) }
-            return Contributions.ExamplesList.Folder(
+            val children = childrenFolders.filter(filter) .map { getSketches(it) }
+            val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) }
+            return Folder(
                 name = name,
                 path = file.absolutePath,
                 children = children,
diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt
index c7bdd7623..43d1cc6ce 100644
--- a/app/src/processing/app/api/Sketchbook.kt
+++ b/app/src/processing/app/api/Sketchbook.kt
@@ -3,10 +3,16 @@ package processing.app.api
 import com.github.ajalt.clikt.command.SuspendingCliktCommand
 import com.github.ajalt.clikt.core.Context
 import com.github.ajalt.clikt.core.subcommands
-import processing.app.Base
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import processing.app.Platform
+import processing.app.Preferences
 import processing.app.api.Sketch.Companion.getSketches
+import java.io.File
 
 class Sketchbook: SuspendingCliktCommand() {
+
+
     override fun help(context: Context) = "Manage the sketchbook"
     override suspend fun run() {
         System.setProperty("java.awt.headless", "true")
@@ -14,12 +20,23 @@ class Sketchbook: SuspendingCliktCommand() {
     init {
         subcommands(SketchbookList())
     }
+
+
     class SketchbookList: SuspendingCliktCommand("list") {
+        val serializer = Json {
+            prettyPrint = true
+        }
+
         override fun help(context: Context) = "List all sketches"
         override suspend fun run() {
-            val sketchbookFolder = Base.getSketchbookFolder()
+            Platform.init()
+            // TODO: Allow the user to change the sketchbook location
+            // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode
+            val sketchbookFolder = Platform.getDefaultSketchbookFolder()
 
-            val sketches = getSketches(sketchbookFolder)
+            val sketches = getSketches(sketchbookFolder.resolve("sketchbook"))
+            val json = serializer.encodeToString(listOf(sketches))
+            println(json)
         }
     }
 }
\ No newline at end of file

From 77a5e9e496f67783ddf2148b054bb9f32f7a9474 Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Fri, 20 Jun 2025 12:00:25 +0200
Subject: [PATCH 5/7] Fix error with copy permissions

---
 app/build.gradle.kts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c40365758..5323a1a82 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -355,6 +355,11 @@ tasks.register<Copy>("includeJavaMode") {
 tasks.register<Copy>("includeJdk") {
     from(Jvm.current().javaHome.absolutePath)
     destinationDir = composeResources("jdk").get().asFile
+
+    fileTree(destinationDir).files.forEach { file ->
+        file.setWritable(true, false)
+        file.setReadable(true, false)
+    }
 }
 tasks.register<Copy>("includeSharedAssets"){
     from("../build/shared/")

From adda0419522ab02e61289590896fba1f70566ca5 Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Fri, 20 Jun 2025 13:48:58 +0200
Subject: [PATCH 6/7] Import cleanup

---
 app/build.gradle.kts      | 2 +-
 gradle/libs.versions.toml | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ec48ba6c9..6adcaa4ea 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -124,7 +124,7 @@ dependencies {
     testImplementation(libs.junitJupiterParams)
 
     implementation(libs.clikt)
-    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
+    implementation(libs.kotlinxSerializationJson)
 }
 
 tasks.test {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 70f93aaff..dfacae1ea 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -28,6 +28,7 @@ jsoup = { module = "org.jsoup:jsoup", version = "1.17.2" }
 markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m2", version = "0.31.0" }
 markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version = "0.31.0" }
 clikt = { module = "com.github.ajalt.clikt:clikt", version = "5.0.2" }
+kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" }
 
 [plugins]
 jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }

From 866c7f819845f2eb86319356fe3d90c1dc3196c8 Mon Sep 17 00:00:00 2001
From: Stef Tervelde <stef@steftervelde.nl>
Date: Fri, 20 Jun 2025 17:29:12 +0200
Subject: [PATCH 7/7] Sketchbook fixes

---
 app/src/processing/app/api/Sketch.kt     | 2 +-
 app/src/processing/app/api/Sketchbook.kt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt
index a0e740014..87f2cc56c 100644
--- a/app/src/processing/app/api/Sketch.kt
+++ b/app/src/processing/app/api/Sketch.kt
@@ -25,7 +25,7 @@ class Sketch {
 
         fun getSketches(file: File, filter: (File) -> Boolean = { true }): Folder {
             val name = file.name
-            val (sketchesFolders, childrenFolders) = file.listFiles()?.partition { isSketchFolder(it) } ?: return Folder(
+            val (sketchesFolders, childrenFolders) = file.listFiles()?.filter (File::isDirectory)?.partition { isSketchFolder(it) } ?: return Folder(
                 name = name,
                 path = file.absolutePath,
                 sketches = emptyList(),
diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt
index 43d1cc6ce..72eec63ed 100644
--- a/app/src/processing/app/api/Sketchbook.kt
+++ b/app/src/processing/app/api/Sketchbook.kt
@@ -34,7 +34,7 @@ class Sketchbook: SuspendingCliktCommand() {
             // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode
             val sketchbookFolder = Platform.getDefaultSketchbookFolder()
 
-            val sketches = getSketches(sketchbookFolder.resolve("sketchbook"))
+            val sketches = getSketches(sketchbookFolder)
             val json = serializer.encodeToString(listOf(sketches))
             println(json)
         }