diff --git a/controller/app/src/main/java/org/appdevforall/k2go/ModuleRegistry.java b/controller/app/src/main/java/org/appdevforall/k2go/ModuleRegistry.java
index 84606c55..cda420d3 100644
--- a/controller/app/src/main/java/org/appdevforall/k2go/ModuleRegistry.java
+++ b/controller/app/src/main/java/org/appdevforall/k2go/ModuleRegistry.java
@@ -38,7 +38,8 @@ public IiabModule(String endpoint, int nameResId, boolean requires64Bit, String
new IiabModule("kolibri", R.string.dash_kolibri, false, "kolibri"), // YAML uses kolibri_install
new IiabModule("maps", R.string.dash_maps, false, "maps"), // YAML uses maps_install
new IiabModule("matomo", R.string.dash_matomo, false, "matomo"), // YAML uses matomo_install
- new IiabModule("forgejo", R.string.dash_forgejo, false, "forgejo") // YAML uses forgejo_install
+ new IiabModule("forgejo", R.string.dash_forgejo, false, "forgejo"), // YAML uses forgejo_install
+ new IiabModule("code-addons", R.string.dash_code_addons, false, "code_addons") // K2GO-99: offline Code on the Go add-ons gallery; YAML uses code_addons_install, served at /code-addons
// ADFA-4842: "dashboard" is intentionally NOT in the roster. It is no longer an
// application/module — it became the REST API core of K2Go; without it maps FQR and all
// REST content downloads fail. It is part of the core system, not a user-installable module.
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/addons/data/AddonsRefreshClient.java b/controller/app/src/main/java/org/appdevforall/k2go/addons/data/AddonsRefreshClient.java
new file mode 100644
index 00000000..5f94a655
--- /dev/null
+++ b/controller/app/src/main/java/org/appdevforall/k2go/addons/data/AddonsRefreshClient.java
@@ -0,0 +1,163 @@
+/*
+ * ============================================================================
+ * Name : AddonsRefreshClient.java
+ * Author : AppDevForAll
+ * Copyright : Copyright (c) 2026 AppDevForAll
+ * Description : K2GO-99. App-side client of the dash-node add-ons refresh
+ * (static/dashboard routes.ts). The box re-mirrors the Code on the
+ * Go add-ons gallery while the server is UP: the device only POSTs
+ * to start and polls a coarse status, so a dropped app never stops
+ * the refresh (the box job is detached). Same POST-then-poll shape
+ * as ForgejoSeedClient.refresh(); the status carries downloaded and
+ * failed counts instead of per-repo outcomes.
+ *
+ * Contract:
+ * POST /k2go-api/addons/refresh -> 202 { state:"running" }
+ * GET /k2go-api/addons/refresh/status -> { state, lines, downloaded, failed }
+ * POST /k2go-api/addons/refresh/cancel -> { state:"cancelled" }
+ * ============================================================================
+ */
+package org.appdevforall.k2go.addons.data;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.appdevforall.k2go.config.BoxEndpoints;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Drives the box add-ons refresh to a terminal state. {@link #refresh} BLOCKS (poll loop), so callers
+ * run it on an IO thread. The box job is detached, so a caller that dies mid-run does not stop it: a
+ * later {@link #refresh} re-attaches by reading the same status (a running refresh is left alone).
+ */
+public final class AddonsRefreshClient {
+
+ /** Terminal verdict of a refresh. */
+ public enum Result { DONE, ERROR, CANCELLED }
+
+ /** Streamed status-tail lines, for a live one-line view. Optional (pass null to ignore). */
+ public interface Listener {
+ void onLine(@NonNull String line);
+ }
+
+ private static final String REFRESH_URL = BoxEndpoints.API + "/addons/refresh";
+ private static final String REFRESH_STATUS_URL = BoxEndpoints.API + "/addons/refresh/status";
+ private static final String REFRESH_CANCEL_URL = BoxEndpoints.API + "/addons/refresh/cancel";
+ private static final long POLL_MS = 2000L;
+ private static final int MAX_POLL_ERRORS = 15; // ~30s of transient blips before giving up
+ // The mirror pulls the full set of .cgp files (hundreds of MB), so the run can take many minutes;
+ // cap the wait so a wedged box job cannot block forever (the box job keeps running detached).
+ private static final long MAX_WAIT_MS = 30 * 60 * 1000L;
+
+ /** The last status-tail line handed to the listener, so a poll that did not advance stays quiet. */
+ private String lastEmitted;
+ private int lastDownloaded = -1, lastFailed = -1;
+
+ /** Files downloaded in the last refresh; -1 if the box did not report it. */
+ public int lastDownloaded() { return lastDownloaded; }
+ /** Files the last refresh could not fetch or verify; -1 if the box did not report it. */
+ public int lastFailed() { return lastFailed; }
+
+ /**
+ * Start the refresh if it is not already running, then poll to a terminal state. Each refresh is
+ * intentional, so there is no "done" short-circuit: unless one is already running (re-attach), it
+ * POSTs a fresh run. The box refresh is safe to re-run (it mirrors into a staging dir and only
+ * swaps the live gallery on success). Returns DONE when the box refresh finished, ERROR on an
+ * unreachable box or timeout, CANCELLED if the user stopped it.
+ */
+ @NonNull
+ public Result refresh(@Nullable Listener l) {
+ String state = readState(l);
+ if (!"running".equals(state)) {
+ if (!post(REFRESH_URL)) return Result.ERROR;
+ }
+ return poll(l);
+ }
+
+ /** Ask the box to stop a running refresh (best-effort; a poll then reads "cancelled"). */
+ public void cancel() {
+ post(REFRESH_CANCEL_URL);
+ }
+
+ @NonNull
+ private Result poll(@Nullable Listener l) {
+ final long deadline = System.currentTimeMillis() + MAX_WAIT_MS;
+ int pollErrors = 0;
+ while (System.currentTimeMillis() < deadline) {
+ try { Thread.sleep(POLL_MS); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return Result.ERROR; }
+ String s = readState(l);
+ if (s == null) { if (++pollErrors > MAX_POLL_ERRORS) return Result.ERROR; continue; }
+ pollErrors = 0;
+ if ("done".equals(s)) return Result.DONE;
+ if ("error".equals(s)) return Result.ERROR;
+ if ("cancelled".equals(s)) return Result.CANCELLED;
+ // "running" (or an unknown transient) -> keep polling.
+ }
+ return Result.ERROR; // timed out; the box job may still finish, a later refresh re-checks
+ }
+
+ /** POST a start/cancel endpoint; true if accepted (2xx) or already running (409). */
+ private boolean post(@NonNull String url) {
+ try {
+ HttpURLConnection c = open("POST", url);
+ int code = c.getResponseCode();
+ c.disconnect();
+ return (code >= 200 && code < 300) || code == 409;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /** GET the status endpoint -> the state string, streaming any new tail line; null on a read error. */
+ @Nullable
+ private String readState(@Nullable Listener l) {
+ try {
+ HttpURLConnection c = open("GET", REFRESH_STATUS_URL);
+ int code = c.getResponseCode();
+ boolean ok = code >= 200 && code < 400;
+ String text = readAll(ok ? c.getInputStream() : c.getErrorStream());
+ c.disconnect();
+ if (!ok) return null;
+ JSONObject j = new JSONObject(text.isEmpty() ? "{}" : text);
+ if (j.has("downloaded")) lastDownloaded = j.optInt("downloaded", lastDownloaded);
+ if (j.has("failed")) lastFailed = j.optInt("failed", lastFailed);
+ if (l != null) {
+ JSONArray lines = j.optJSONArray("lines");
+ if (lines != null && lines.length() > 0) {
+ String last = lines.optString(lines.length() - 1, "");
+ if (!last.isEmpty() && !last.equals(lastEmitted)) { lastEmitted = last; l.onLine(last); }
+ }
+ }
+ return j.optString("state", "");
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static HttpURLConnection open(String method, String urlStr) throws Exception {
+ HttpURLConnection c = (HttpURLConnection) new URL(urlStr).openConnection();
+ c.setUseCaches(false);
+ c.setConnectTimeout(4000);
+ c.setReadTimeout(4000);
+ c.setRequestMethod(method);
+ c.setRequestProperty("Accept", "application/json");
+ return c;
+ }
+
+ private static String readAll(InputStream is) throws Exception {
+ if (is == null) return "";
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ byte[] chunk = new byte[4096];
+ int n;
+ while ((n = is.read(chunk)) != -1) buf.write(chunk, 0, n);
+ is.close();
+ return buf.toString(StandardCharsets.UTF_8.name());
+ }
+}
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/addons/presentation/AddonsRefresh.java b/controller/app/src/main/java/org/appdevforall/k2go/addons/presentation/AddonsRefresh.java
new file mode 100644
index 00000000..ad880c2d
--- /dev/null
+++ b/controller/app/src/main/java/org/appdevforall/k2go/addons/presentation/AddonsRefresh.java
@@ -0,0 +1,147 @@
+/*
+ * ============================================================================
+ * Name : AddonsRefresh.java
+ * Author : AppDevForAll
+ * Copyright : Copyright (c) 2026 AppDevForAll
+ * Description : K2GO-99. The single "Update add-ons" flow, shared by the module detail button and the
+ * module action sheet row so neither duplicates it. Modeled on ForgejoRepoRefresh: it
+ * gates like the dashboard update (needs internet, then metered consent), shows minimal
+ * inline progress (a description, a live one-line output tail, an indeterminate bar and a
+ * Cancel) injected right after the trigger view, runs the box refresh on an IO thread, and
+ * reports the outcome in a snackbar.
+ *
+ * Lifecycle: there is NO persistent app-side state. The box refresh job is detached
+ * (setsid), so a host that goes away mid-run just drops the UI updates (guarded by
+ * View.isAttachedToWindow()); the box finishes on its own and the gallery is swapped in
+ * only on success. The only state is the box's own status/pid files, which the box manages.
+ * ============================================================================
+ */
+package org.appdevforall.k2go.addons.presentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.progressindicator.LinearProgressIndicator;
+
+import org.appdevforall.k2go.R;
+import org.appdevforall.k2go.addons.data.AddonsRefreshClient;
+import org.appdevforall.k2go.util.AppExecutors;
+import org.appdevforall.k2go.util.Snackbars;
+
+public final class AddonsRefresh {
+
+ private AddonsRefresh() {}
+
+ /**
+ * Gate (internet, then metered consent) then run the refresh with progress injected right after
+ * {@code trigger}. The trigger stays in place (only disabled) as a visible anchor for the snackbar.
+ */
+ public static void start(@NonNull Activity act, @NonNull View trigger) {
+ if (!org.appdevforall.k2go.networkpolicy.data.AndroidNetworkClassifier.hasInternet(act)) {
+ Snackbars.make(trigger, act.getString(R.string.k2go_dash_needs_internet)).show();
+ return;
+ }
+ // Same metered gate as the dashboard update: the box downloads the gallery over the device network.
+ org.appdevforall.k2go.networkpolicy.presentation.NetworkPolicyGate.guardHeavyStart(act, () -> run(trigger));
+ }
+
+ private static void run(@NonNull View trigger) {
+ final ViewGroup parent = (ViewGroup) trigger.getParent();
+ if (parent == null || !trigger.isAttachedToWindow()) return; // host went away during the gate
+ final Context ctx = trigger.getContext();
+ final Handler main = new Handler(Looper.getMainLooper());
+ final float d = ctx.getResources().getDisplayMetrics().density;
+ final int side = Math.round(20 * d);
+
+ final LinearLayout progress = new LinearLayout(ctx);
+ progress.setOrientation(LinearLayout.VERTICAL);
+ LinearLayout.LayoutParams plp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ plp.leftMargin = side; plp.rightMargin = side; plp.topMargin = Math.round(8 * d);
+ progress.setLayoutParams(plp);
+
+ final TextView label = new TextView(ctx);
+ label.setText(R.string.k2go_code_addons_updating);
+ label.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall);
+ label.setTextColor(ContextCompat.getColor(ctx, R.color.k2go_muted));
+ progress.addView(label);
+
+ final TextView liveLine = new TextView(ctx);
+ liveLine.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall);
+ liveLine.setTextColor(ContextCompat.getColor(ctx, R.color.k2go_muted));
+ liveLine.setMaxLines(1);
+ liveLine.setEllipsize(TextUtils.TruncateAt.END);
+ LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ llp.topMargin = Math.round(2 * d);
+ liveLine.setLayoutParams(llp);
+ progress.addView(liveLine);
+
+ final LinearLayout barLine = new LinearLayout(ctx);
+ barLine.setOrientation(LinearLayout.HORIZONTAL);
+ barLine.setGravity(Gravity.CENTER_VERTICAL);
+ LinearLayout.LayoutParams barLineLp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ barLineLp.topMargin = Math.round(4 * d);
+ barLine.setLayoutParams(barLineLp);
+
+ final LinearProgressIndicator bar = new LinearProgressIndicator(ctx);
+ bar.setIndeterminate(true);
+ LinearLayout.LayoutParams blp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
+ bar.setLayoutParams(blp);
+ barLine.addView(bar);
+
+ final TextView cancel = new TextView(ctx);
+ cancel.setText(R.string.k2go_dash_cancel);
+ cancel.setAllCaps(true);
+ cancel.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelLarge);
+ cancel.setTextColor(ContextCompat.getColor(ctx, R.color.k2go_teal));
+ int hp = Math.round(12 * d), vp = Math.round(6 * d);
+ cancel.setPadding(hp, vp, hp, vp);
+ barLine.addView(cancel);
+ progress.addView(barLine);
+
+ parent.addView(progress, parent.indexOfChild(trigger) + 1);
+ trigger.setEnabled(false); // stays in place as an anchor; re-enabled when the refresh settles
+
+ cancel.setOnClickListener(cv -> {
+ cancel.setEnabled(false);
+ label.setText(R.string.k2go_code_addons_update_cancelling);
+ AppExecutors.get().io().execute(() -> new AddonsRefreshClient().cancel());
+ });
+
+ AppExecutors.get().io().execute(() -> {
+ final AddonsRefreshClient client = new AddonsRefreshClient();
+ final AddonsRefreshClient.Result r = client.refresh(rawLine -> {
+ final String shown = rawLine.trim();
+ main.post(() -> { if (liveLine.isAttachedToWindow()) liveLine.setText(shown); });
+ });
+ final int failed = client.lastFailed();
+ main.post(() -> {
+ if (!trigger.isAttachedToWindow()) return;
+ parent.removeView(progress);
+ trigger.setEnabled(true);
+ Snackbars.make(trigger, ctx.getString(messageFor(r, failed))).show();
+ });
+ });
+ }
+
+ /** Map the refresh outcome to a user message covering every state. */
+ private static int messageFor(AddonsRefreshClient.Result r, int failed) {
+ if (r == AddonsRefreshClient.Result.CANCELLED) return R.string.k2go_code_addons_update_cancelled;
+ if (r != AddonsRefreshClient.Result.DONE) return R.string.k2go_code_addons_update_failed; // box unreachable
+ if (failed > 0) return R.string.k2go_code_addons_update_some_failed; // some files could not be fetched
+ return R.string.k2go_code_addons_update_done;
+ }
+}
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/dependency/domain/DashNodeRequirement.java b/controller/app/src/main/java/org/appdevforall/k2go/dependency/domain/DashNodeRequirement.java
index 7db6f0a8..abc886a6 100644
--- a/controller/app/src/main/java/org/appdevforall/k2go/dependency/domain/DashNodeRequirement.java
+++ b/controller/app/src/main/java/org/appdevforall/k2go/dependency/domain/DashNodeRequirement.java
@@ -9,7 +9,9 @@
*
* Forgejo needs the dashboard build that ships its repo status/refresh endpoints (1.3.7):
* an older dash-node makes "Install repos"/"Update repos" and the seed break silently, so
- * installing Forgejo is HARD-blocked below that version. Other modules have no hard minimum
+ * installing Forgejo is HARD-blocked below that version. Code on the Go add-ons (K2GO-99)
+ * needs the dashboard build that ships the /addons/refresh endpoints (1.3.8), so the gallery
+ * can update, so it is HARD-blocked below that version too. Other modules have no hard minimum
* (a soft "update the dashboard" suggestion applies to all installs, handled in the gate).
* ============================================================================
*/
@@ -24,6 +26,7 @@ private DashNodeRequirement() {}
@Nullable
public static int[] minFor(@Nullable String moduleKey) {
if ("forgejo".equals(moduleKey)) return new int[]{1, 3, 7};
+ if ("code_addons".equals(moduleKey)) return new int[]{1, 3, 8}; // K2GO-99: needs /addons/refresh
return null;
}
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java
index c4414a4b..b0d7c2c1 100644
--- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java
+++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java
@@ -181,6 +181,7 @@ private void populateCards() {
cards.clear();
cards.add(new Card("books", getString(R.string.k2go_card_books), false, R.drawable.ic_card_book));
cards.add(new Card("code", getString(R.string.k2go_card_code), false, R.drawable.ic_card_code));
+ cards.add(new Card("code-addons", getString(R.string.k2go_card_code_addons), false, R.drawable.ic_card_code));
cards.add(new Card("forgejo", getString(R.string.k2go_card_forgejo), false, R.drawable.ic_card_forgejo));
cards.add(new Card("kiwix", getString(R.string.k2go_card_wikipedia), true, R.drawable.ic_card_wikipedia));
cards.add(new Card("kolibri", getString(R.string.k2go_card_courses), false, R.drawable.ic_card_courses));
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java
index 59887d34..2e4e174b 100644
--- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java
+++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java
@@ -223,6 +223,14 @@ public static void show(Activity act, String endpoint, String title, int iconRes
});
});
}
+ // K2GO-99: an installed add-ons gallery offers a live refresh here (the module action
+ // menu). No status gate (installed means there is a gallery); the shared flow gates
+ // internet, then runs the refresh IN the sheet (it does NOT dismiss).
+ if ("code_addons".equals(key)) {
+ content.addView(row(ctx, R.drawable.ic_refresh,
+ act.getString(R.string.k2go_code_addons_update), Emphasis.ACCENT, null, false,
+ v -> org.appdevforall.k2go.addons.presentation.AddonsRefresh.start(act, v)));
+ }
break;
case SCHEDULED: {
content.addView(about);
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleCards.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleCards.java
index 319afd8a..5900eb66 100644
--- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleCards.java
+++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleCards.java
@@ -72,6 +72,9 @@ public boolean runsOnThisRuntime() {
new Card("code", R.string.k2go_mod_code_title, R.string.k2go_mod_code_title,
R.string.k2go_mod_code_sub, R.string.k2go_mod_code_desc,
R.drawable.ic_card_code, false),
+ new Card("code_addons", R.string.k2go_mod_code_addons_title, R.string.k2go_mod_code_addons_title,
+ R.string.k2go_mod_code_addons_sub, R.string.k2go_mod_code_addons_desc,
+ R.drawable.ic_card_code, false), // K2GO-99: offline add-ons gallery (reuses the code icon)
new Card("forgejo", R.string.k2go_mod_forgejo_title, R.string.k2go_mod_forgejo_title,
R.string.k2go_mod_forgejo_sub, R.string.k2go_mod_forgejo_desc,
R.drawable.ic_card_forgejo, false),
@@ -133,6 +136,7 @@ public static String license(String key) {
public static int sizeLabelRes(String key) {
if ("maps".equals(key)) return R.string.k2go_mod_maps_size;
if ("matomo".equals(key)) return R.string.k2go_mod_matomo_size; // ADFA-4958: curated (~114 MB, incl. MariaDB + tarball)
+ if ("code_addons".equals(key)) return R.string.k2go_mod_code_addons_size; // K2GO-99: curated (~600 MB of .cgp; grows with the add-on set)
return 0;
}
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java
index 4389aba7..e385f769 100644
--- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java
+++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java
@@ -112,6 +112,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
final com.google.android.material.checkbox.MaterialCheckBox forgejoRepos =
root.findViewById(R.id.k2go_moddet_forgejo_repos);
final boolean isForgejo = "forgejo".equals(c.key());
+ final boolean isCodeAddons = "code_addons".equals(c.key());
// K2GO-417: the repos opt-in is an INSTALL-TIME choice, so it is shown ONLY in the installable
// branch below (default-checked in the layout). Once the module is installed it stays GONE:
// toggling it would do nothing (roles are not reinstalled from here, and unchecking cannot remove
@@ -197,6 +198,15 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
});
});
}
+ if (isCodeAddons) {
+ // K2GO-99: an installed add-ons gallery offers a live refresh (re-mirror the
+ // published gallery). Minimal inline progress via the shared flow; no status gate
+ // (unlike Forgejo there is no sub-state: installed means there is a gallery).
+ installNowBtn.setText(R.string.k2go_code_addons_update);
+ installNowBtn.setOnClickListener(v ->
+ org.appdevforall.k2go.addons.presentation.AddonsRefresh.start(requireActivity(), installNowBtn));
+ installNowBtn.setVisibility(View.VISIBLE);
+ }
return; // a module cannot be uninstalled or reinstalled here (repos action aside)
}
// ADFA-4898: this module's runrole failed in the last finished batch — the SAME per-module
diff --git a/controller/app/src/main/res/values-ar/strings.xml b/controller/app/src/main/res/values-ar/strings.xml
index b4ef83e5..7cfcc7aa 100644
--- a/controller/app/src/main/res/values-ar/strings.xml
+++ b/controller/app/src/main/res/values-ar/strings.xml
@@ -1375,4 +1375,14 @@
يتوفر تحديث لـ dashboard
يُوصى بتحديث dashboard أولاً حتى تعمل ميزات الوحدة الجديدة بشكل صحيح.
التثبيت على أي حال
+ إضافات وقوالب ومقتطفات للمحرر
+ معرض غير متصل بالإنترنت للإضافات الخاصة بـ Code on the Go: الإضافات والقوالب والمقتطفات وإجراءات التعليمات البرمجية. تصفّحها وصفّها ونزّلها دون اتصال بالإنترنت.
+ الإضافات
+ تحديث الإضافات
+ جارٍ تحديث الإضافات...
+ جارٍ الإلغاء...
+ تم إلغاء تحديث الإضافات
+ تم تحديث الإضافات
+ تعذّر تحديث الإضافات
+ تعذّر تحديث بعض الإضافات
diff --git a/controller/app/src/main/res/values-az/strings.xml b/controller/app/src/main/res/values-az/strings.xml
index 8be25bf2..576078e8 100644
--- a/controller/app/src/main/res/values-az/strings.xml
+++ b/controller/app/src/main/res/values-az/strings.xml
@@ -1395,4 +1395,14 @@
Dashboard yenilemesi movcuddur
Yeni modul funksiyalarinin duzgun islemesi ucun evvelce dashboard-u yenilemek tovsiye olunur.
Yene de qurasdir
+ Redaktor üçün plaginlər, şablonlar və kod parçaları
+ Code on the Go üçün oflayn əlavələr qalereyası: plaginlər, şablonlar, kod parçaları və kod əməliyyatları. İnternet bağlantısı olmadan nəzər salın, süzün və endirin.
+ Əlavələr
+ Əlavələri yenilə
+ Əlavələr yenilənir...
+ Ləğv edilir...
+ Əlavələrin yenilənməsi ləğv edildi
+ Əlavələr yeniləndi
+ Əlavələri yeniləmək mümkün olmadı
+ Bəzi əlavələri yeniləmək mümkün olmadı
diff --git a/controller/app/src/main/res/values-bg/strings.xml b/controller/app/src/main/res/values-bg/strings.xml
index 2a523056..a220548a 100644
--- a/controller/app/src/main/res/values-bg/strings.xml
+++ b/controller/app/src/main/res/values-bg/strings.xml
@@ -1382,4 +1382,14 @@
Налична е актуализация на dashboard
Препоръчва се първо да обновите dashboard, за да работят правилно новите функции на модула.
Инсталирай въпреки това
+ Добавки, шаблони и фрагменти за редактора
+ Офлайн галерия с добавки за Code on the Go: добавки, шаблони, фрагменти и действия с код. Разглеждайте, филтрирайте и изтегляйте без интернет връзка.
+ Добавки
+ Актуализиране на добавките
+ Актуализиране на добавките...
+ Отказване...
+ Актуализирането на добавките е отменено
+ Добавките са актуализирани
+ Добавките не могат да бъдат актуализирани
+ Някои добавки не можаха да бъдат актуализирани
diff --git a/controller/app/src/main/res/values-bn/strings.xml b/controller/app/src/main/res/values-bn/strings.xml
index 140e136e..3cce6b29 100644
--- a/controller/app/src/main/res/values-bn/strings.xml
+++ b/controller/app/src/main/res/values-bn/strings.xml
@@ -1388,4 +1388,14 @@
dashboard আপডেট উপলব্ধ
মডিউলের নতুন বৈশিষ্ট্য ঠিকমতো কাজ করার জন্য প্রথমে dashboard আপডেট করার পরামর্শ দেওয়া হয়।
তবুও ইনস্টল করুন
+ সম্পাদকের জন্য প্লাগইন, টেমপ্লেট এবং স্নিপেট
+ Code on the Go-এর জন্য অ্যাড-অনের একটি অফলাইন গ্যালারি: প্লাগইন, টেমপ্লেট, স্নিপেট এবং কোড অ্যাকশন। ইন্টারনেট সংযোগ ছাড়াই ব্রাউজ, ফিল্টার এবং ডাউনলোড করুন।
+ অ্যাড-অন
+ অ্যাড-অন আপডেট করুন
+ অ্যাড-অন আপডেট হচ্ছে...
+ বাতিল করা হচ্ছে...
+ অ্যাড-অন আপডেট বাতিল করা হয়েছে
+ অ্যাড-অন আপডেট করা হয়েছে
+ অ্যাড-অন আপডেট করা যায়নি
+ কিছু অ্যাড-অন আপডেট করা যায়নি
diff --git a/controller/app/src/main/res/values-cs/strings.xml b/controller/app/src/main/res/values-cs/strings.xml
index 019882aa..33bd2355 100644
--- a/controller/app/src/main/res/values-cs/strings.xml
+++ b/controller/app/src/main/res/values-cs/strings.xml
@@ -1382,4 +1382,14 @@
Je dostupna aktualizace dashboardu
Doporucujeme nejprve aktualizovat dashboard, aby nove funkce modulu fungovaly spravne.
Presto nainstalovat
+ Pluginy, šablony a úryvky pro editor
+ Offline galerie doplňků pro Code on the Go: pluginy, šablony, úryvky a akce kódu. Procházejte, filtrujte a stahujte je bez připojení k internetu.
+ Doplňky
+ Aktualizovat doplňky
+ Aktualizace doplňků...
+ Rušení...
+ Aktualizace doplňků zrušena
+ Doplňky aktualizovány
+ Doplňky se nepodařilo aktualizovat
+ Některé doplňky se nepodařilo aktualizovat
diff --git a/controller/app/src/main/res/values-de/strings.xml b/controller/app/src/main/res/values-de/strings.xml
index ab0463d5..1cd62eba 100644
--- a/controller/app/src/main/res/values-de/strings.xml
+++ b/controller/app/src/main/res/values-de/strings.xml
@@ -1375,4 +1375,14 @@
Ein Dashboard-Update ist verfuegbar
Es wird empfohlen, zuerst das Dashboard zu aktualisieren, damit neue Modulfunktionen richtig laufen.
Trotzdem installieren
+ Plugins, Vorlagen und Snippets für den Editor
+ Eine Offline-Galerie mit Add-ons für Code on the Go: Plugins, Vorlagen, Snippets und Code-Aktionen. Durchsuchen, filtern und ohne Internetverbindung herunterladen.
+ Add-ons
+ Add-ons aktualisieren
+ Add-ons werden aktualisiert...
+ Wird abgebrochen...
+ Add-on-Aktualisierung abgebrochen
+ Add-ons aktualisiert
+ Add-ons konnten nicht aktualisiert werden
+ Einige Add-ons konnten nicht aktualisiert werden
diff --git a/controller/app/src/main/res/values-el/strings.xml b/controller/app/src/main/res/values-el/strings.xml
index abf4a288..2945b31c 100644
--- a/controller/app/src/main/res/values-el/strings.xml
+++ b/controller/app/src/main/res/values-el/strings.xml
@@ -1382,4 +1382,14 @@
Διαθέσιμη ενημέρωση dashboard
Συνιστάται να ενημερώσετε πρώτα το dashboard ώστε οι νέες λειτουργίες της μονάδας να λειτουργούν σωστά.
Εγκατάσταση ούτως ή άλλως
+ Πρόσθετα, πρότυπα και αποσπάσματα για τον επεξεργαστή
+ Μια συλλογή προσθέτων εκτός σύνδεσης για το Code on the Go: πρόσθετα, πρότυπα, αποσπάσματα και ενέργειες κώδικα. Περιηγηθείτε, φιλτράρετε και κατεβάστε τα χωρίς σύνδεση στο διαδίκτυο.
+ Πρόσθετα
+ Ενημέρωση προσθέτων
+ Ενημέρωση προσθέτων...
+ Ακύρωση...
+ Η ενημέρωση προσθέτων ακυρώθηκε
+ Τα πρόσθετα ενημερώθηκαν
+ Δεν ήταν δυνατή η ενημέρωση των προσθέτων
+ Ορισμένα πρόσθετα δεν ήταν δυνατό να ενημερωθούν
diff --git a/controller/app/src/main/res/values-es/strings.xml b/controller/app/src/main/res/values-es/strings.xml
index 1bc2d834..1f80d2f1 100644
--- a/controller/app/src/main/res/values-es/strings.xml
+++ b/controller/app/src/main/res/values-es/strings.xml
@@ -1451,4 +1451,14 @@
Hay una actualizacion del dashboard disponible
Se recomienda actualizar primero el dashboard para que las nuevas funciones del modulo funcionen bien.
Instalar de todos modos
+ Plugins, plantillas y fragmentos para el editor
+ Una galería sin conexión de complementos para Code on the Go: plugins, plantillas, fragmentos y acciones de código. Explóralos, fíltralos y descárgalos sin conexión a internet.
+ Complementos
+ Actualizar complementos
+ Actualizando complementos...
+ Cancelando...
+ Actualización de complementos cancelada
+ Complementos actualizados
+ No se pudieron actualizar los complementos
+ Algunos complementos no se pudieron actualizar
diff --git a/controller/app/src/main/res/values-fa/strings.xml b/controller/app/src/main/res/values-fa/strings.xml
index ff487636..4cdb967c 100644
--- a/controller/app/src/main/res/values-fa/strings.xml
+++ b/controller/app/src/main/res/values-fa/strings.xml
@@ -1375,4 +1375,14 @@
بهروزرسانی dashboard در دسترس است
توصیه میشود ابتدا dashboard را بهروزرسانی کنید تا قابلیتهای جدید ماژول درست کار کنند.
به هر حال نصب شود
+ افزونهها، قالبها و قطعهکدها برای ویرایشگر
+ یک گالری آفلاین از افزونهها برای Code on the Go: افزونهها، قالبها، قطعهکدها و کنشهای کد. بدون اتصال به اینترنت آنها را مرور، فیلتر و دانلود کنید.
+ افزونهها
+ بهروزرسانی افزونهها
+ در حال بهروزرسانی افزونهها...
+ در حال لغو...
+ بهروزرسانی افزونهها لغو شد
+ افزونهها بهروزرسانی شدند
+ بهروزرسانی افزونهها ممکن نشد
+ برخی افزونهها بهروزرسانی نشدند
diff --git a/controller/app/src/main/res/values-fr/strings.xml b/controller/app/src/main/res/values-fr/strings.xml
index 9cc3ff59..184129e5 100644
--- a/controller/app/src/main/res/values-fr/strings.xml
+++ b/controller/app/src/main/res/values-fr/strings.xml
@@ -1462,4 +1462,14 @@
Une mise a jour du dashboard est disponible
Il est recommande de mettre a jour le dashboard d\'abord pour que les nouvelles fonctions du module marchent bien.
Installer quand meme
+ Plugins, modèles et extraits pour l\'éditeur
+ Une galerie hors ligne de modules complémentaires pour Code on the Go : plugins, modèles, extraits et actions de code. Parcourez-les, filtrez-les et téléchargez-les sans connexion internet.
+ Modules
+ Mettre à jour les modules
+ Mise à jour des modules...
+ Annulation...
+ Mise à jour des modules annulée
+ Modules mis à jour
+ Impossible de mettre à jour les modules
+ Certains modules n\'ont pas pu être mis à jour
diff --git a/controller/app/src/main/res/values-gu/strings.xml b/controller/app/src/main/res/values-gu/strings.xml
index 36783002..c2bba2be 100644
--- a/controller/app/src/main/res/values-gu/strings.xml
+++ b/controller/app/src/main/res/values-gu/strings.xml
@@ -1388,4 +1388,14 @@
dashboard અપડેટ ઉપલબ્ધ છે
મૉડ્યૂલની નવી સુવિધાઓ યોગ્ય રીતે કામ કરે તે માટે પહેલાં dashboard અપડેટ કરવાની ભલામણ છે.
તોપણ ઇન્સ્ટૉલ કરો
+ એડિટર માટે પ્લગઇન, ટેમ્પ્લેટ અને સ્નિપેટ
+ Code on the Go માટે એડ-ઓનની ઑફલાઇન ગેલેરી: પ્લગઇન, ટેમ્પ્લેટ, સ્નિપેટ અને કોડ ક્રિયાઓ. ઇન્ટરનેટ કનેક્શન વિના બ્રાઉઝ કરો, ફિલ્ટર કરો અને ડાઉનલોડ કરો.
+ એડ-ઓન
+ એડ-ઓન અપડેટ કરો
+ એડ-ઓન અપડેટ થઈ રહ્યા છે...
+ રદ કરી રહ્યું છે...
+ એડ-ઓન અપડેટ રદ કરાયું
+ એડ-ઓન અપડેટ થયા
+ એડ-ઓન અપડેટ કરી શકાયા નહીં
+ કેટલાક એડ-ઓન અપડેટ કરી શકાયા નહીં
diff --git a/controller/app/src/main/res/values-hi/strings.xml b/controller/app/src/main/res/values-hi/strings.xml
index ea2fbd22..3d2dde69 100644
--- a/controller/app/src/main/res/values-hi/strings.xml
+++ b/controller/app/src/main/res/values-hi/strings.xml
@@ -1452,4 +1452,14 @@
dashboard अपडेट उपलब्ध है
अनुशंसित है कि पहले dashboard अपडेट करें ताकि मॉड्यूल की नई सुविधाएँ सही से काम करें।
फिर भी इंस्टॉल करें
+ संपादक के लिए प्लगइन, टेम्पलेट और स्निपेट
+ Code on the Go के लिए ऐड-ऑन की ऑफ़लाइन गैलरी: प्लगइन, टेम्पलेट, स्निपेट और कोड क्रियाएँ। बिना इंटरनेट कनेक्शन के ब्राउज़ करें, फ़िल्टर करें और डाउनलोड करें।
+ ऐड-ऑन
+ ऐड-ऑन अपडेट करें
+ ऐड-ऑन अपडेट हो रहे हैं...
+ रद्द किया जा रहा है...
+ ऐड-ऑन अपडेट रद्द किया गया
+ ऐड-ऑन अपडेट किए गए
+ ऐड-ऑन अपडेट नहीं हो सके
+ कुछ ऐड-ऑन अपडेट नहीं हो सके
diff --git a/controller/app/src/main/res/values-hu/strings.xml b/controller/app/src/main/res/values-hu/strings.xml
index f0297ed2..88dd3736 100644
--- a/controller/app/src/main/res/values-hu/strings.xml
+++ b/controller/app/src/main/res/values-hu/strings.xml
@@ -1375,4 +1375,14 @@
Elerheto a dashboard frissitese
Ajanlott eloszor frissiteni a dashboardot, hogy az uj modulfunkciok megfeleloen mukodjenek.
Telepites mindenkepp
+ Beépülők, sablonok és kódrészletek a szerkesztőhöz
+ Kiegészítők offline galériája a Code on the Go-hoz: beépülők, sablonok, kódrészletek és kódműveletek. Böngéssze, szűrje és töltse le őket internetkapcsolat nélkül.
+ Kiegészítők
+ Kiegészítők frissítése
+ Kiegészítők frissítése...
+ Megszakítás...
+ Kiegészítők frissítése megszakítva
+ Kiegészítők frissítve
+ A kiegészítőket nem sikerült frissíteni
+ Néhány kiegészítőt nem sikerült frissíteni
diff --git a/controller/app/src/main/res/values-in/strings.xml b/controller/app/src/main/res/values-in/strings.xml
index b0f71c5a..77116249 100644
--- a/controller/app/src/main/res/values-in/strings.xml
+++ b/controller/app/src/main/res/values-in/strings.xml
@@ -1382,4 +1382,14 @@
Pembaruan dashboard tersedia
Disarankan memperbarui dashboard dulu agar fitur baru modul berfungsi dengan benar.
Tetap pasang
+ Plugin, templat, dan cuplikan untuk editor
+ Galeri offline add-on untuk Code on the Go: plugin, templat, cuplikan, dan tindakan kode. Jelajahi, filter, dan unduh tanpa koneksi internet.
+ Add-on
+ Perbarui add-on
+ Memperbarui add-on...
+ Membatalkan...
+ Pembaruan add-on dibatalkan
+ Add-on diperbarui
+ Tidak dapat memperbarui add-on
+ Beberapa add-on tidak dapat diperbarui
diff --git a/controller/app/src/main/res/values-it/strings.xml b/controller/app/src/main/res/values-it/strings.xml
index 11991c6e..80236612 100644
--- a/controller/app/src/main/res/values-it/strings.xml
+++ b/controller/app/src/main/res/values-it/strings.xml
@@ -1375,4 +1375,14 @@
E disponibile un aggiornamento del dashboard
Si consiglia di aggiornare prima il dashboard affinche le nuove funzioni del modulo funzionino bene.
Installa comunque
+ Plugin, modelli e snippet per l\'editor
+ Una galleria offline di componenti aggiuntivi per Code on the Go: plugin, modelli, snippet e azioni di codice. Sfogliali, filtrali e scaricali senza connessione internet.
+ Componenti aggiuntivi
+ Aggiorna componenti aggiuntivi
+ Aggiornamento dei componenti aggiuntivi...
+ Annullamento...
+ Aggiornamento dei componenti aggiuntivi annullato
+ Componenti aggiuntivi aggiornati
+ Impossibile aggiornare i componenti aggiuntivi
+ Alcuni componenti aggiuntivi non sono stati aggiornati
diff --git a/controller/app/src/main/res/values-ja/strings.xml b/controller/app/src/main/res/values-ja/strings.xml
index 0ba9ccf6..faaa547f 100644
--- a/controller/app/src/main/res/values-ja/strings.xml
+++ b/controller/app/src/main/res/values-ja/strings.xml
@@ -1376,4 +1376,14 @@
dashboard の更新があります
モジュールの新機能が正しく動作するよう、先に dashboard を更新することをおすすめします。
それでもインストール
+ エディター用のプラグイン、テンプレート、スニペット
+ Code on the Go 用のアドオンのオフラインギャラリー:プラグイン、テンプレート、スニペット、コードアクション。インターネット接続なしで閲覧、絞り込み、ダウンロードできます。
+ アドオン
+ アドオンを更新
+ アドオンを更新しています...
+ キャンセルしています...
+ アドオンの更新をキャンセルしました
+ アドオンを更新しました
+ アドオンを更新できませんでした
+ 一部のアドオンを更新できませんでした
diff --git a/controller/app/src/main/res/values-ko/strings.xml b/controller/app/src/main/res/values-ko/strings.xml
index 709600e0..1412bfbf 100644
--- a/controller/app/src/main/res/values-ko/strings.xml
+++ b/controller/app/src/main/res/values-ko/strings.xml
@@ -1376,4 +1376,14 @@
dashboard 업데이트가 있습니다
모듈의 새 기능이 제대로 작동하도록 먼저 dashboard를 업데이트하는 것이 좋습니다.
무시하고 설치
+ 편집기용 플러그인, 템플릿, 스니펫
+ Code on the Go용 애드온의 오프라인 갤러리: 플러그인, 템플릿, 스니펫, 코드 작업. 인터넷 연결 없이 찾아보고 필터링하고 다운로드하세요.
+ 애드온
+ 애드온 업데이트
+ 애드온 업데이트 중...
+ 취소 중...
+ 애드온 업데이트가 취소됨
+ 애드온이 업데이트됨
+ 애드온을 업데이트할 수 없음
+ 일부 애드온을 업데이트하지 못함
diff --git a/controller/app/src/main/res/values-lt/strings.xml b/controller/app/src/main/res/values-lt/strings.xml
index 33be7507..77b83424 100644
--- a/controller/app/src/main/res/values-lt/strings.xml
+++ b/controller/app/src/main/res/values-lt/strings.xml
@@ -1392,4 +1392,14 @@
Yra dashboard atnaujinimas
Rekomenduojama pirma atnaujinti dashboard, kad naujos modulio funkcijos veiktu tinkamai.
Vis tiek idiegti
+ Papildiniai, šablonai ir fragmentai redaktoriui
+ Neprisijungus veikianti Code on the Go priedų galerija: papildiniai, šablonai, fragmentai ir kodo veiksmai. Naršykite, filtruokite ir atsisiųskite juos be interneto ryšio.
+ Priedai
+ Atnaujinti priedus
+ Atnaujinami priedai...
+ Atšaukiama...
+ Priedų atnaujinimas atšauktas
+ Priedai atnaujinti
+ Nepavyko atnaujinti priedų
+ Kai kurių priedų nepavyko atnaujinti
diff --git a/controller/app/src/main/res/values-nl/strings.xml b/controller/app/src/main/res/values-nl/strings.xml
index a29b141f..397b1e8e 100644
--- a/controller/app/src/main/res/values-nl/strings.xml
+++ b/controller/app/src/main/res/values-nl/strings.xml
@@ -1375,4 +1375,14 @@
Er is een dashboard-update beschikbaar
Het wordt aanbevolen eerst het dashboard bij te werken zodat nieuwe modulefuncties goed werken.
Toch installeren
+ Plug-ins, sjablonen en snippets voor de editor
+ Een offline galerij met add-ons voor Code on the Go: plug-ins, sjablonen, snippets en code-acties. Blader, filter en download ze zonder internetverbinding.
+ Add-ons
+ Add-ons bijwerken
+ Add-ons bijwerken...
+ Annuleren...
+ Bijwerken van add-ons geannuleerd
+ Add-ons bijgewerkt
+ Kan de add-ons niet bijwerken
+ Sommige add-ons konden niet worden bijgewerkt
diff --git a/controller/app/src/main/res/values-no/strings.xml b/controller/app/src/main/res/values-no/strings.xml
index e25e271b..058d0c76 100644
--- a/controller/app/src/main/res/values-no/strings.xml
+++ b/controller/app/src/main/res/values-no/strings.xml
@@ -1385,4 +1385,14 @@
En dashboard-oppdatering er tilgjengelig
Det anbefales a oppdatere dashboard forst slik at nye modulfunksjoner fungerer riktig.
Installer likevel
+ Programtillegg, maler og utdrag for redigereren
+ Et frakoblet galleri med tillegg for Code on the Go: programtillegg, maler, utdrag og kodehandlinger. Bla gjennom, filtrer og last dem ned uten internettforbindelse.
+ Tillegg
+ Oppdater tillegg
+ Oppdaterer tillegg...
+ Avbryter...
+ Oppdatering av tillegg avbrutt
+ Tillegg oppdatert
+ Kunne ikke oppdatere tilleggene
+ Noen tillegg kunne ikke oppdateres
diff --git a/controller/app/src/main/res/values-pl/strings.xml b/controller/app/src/main/res/values-pl/strings.xml
index 75787a63..3a544ad6 100644
--- a/controller/app/src/main/res/values-pl/strings.xml
+++ b/controller/app/src/main/res/values-pl/strings.xml
@@ -1385,4 +1385,14 @@
Dostepna jest aktualizacja dashboardu
Zalecane jest najpierw zaktualizowanie dashboardu, aby nowe funkcje modulu dzialaly poprawnie.
Zainstaluj mimo to
+ Wtyczki, szablony i fragmenty dla edytora
+ Galeria offline dodatków do Code on the Go: wtyczki, szablony, fragmenty i akcje kodu. Przeglądaj, filtruj i pobieraj je bez połączenia z internetem.
+ Dodatki
+ Zaktualizuj dodatki
+ Aktualizowanie dodatków...
+ Anulowanie...
+ Anulowano aktualizację dodatków
+ Zaktualizowano dodatki
+ Nie można zaktualizować dodatków
+ Niektórych dodatków nie udało się zaktualizować
diff --git a/controller/app/src/main/res/values-pt/strings.xml b/controller/app/src/main/res/values-pt/strings.xml
index 770b3c8d..b6d847c7 100644
--- a/controller/app/src/main/res/values-pt/strings.xml
+++ b/controller/app/src/main/res/values-pt/strings.xml
@@ -1454,4 +1454,14 @@
Ha uma atualizacao do dashboard disponivel
Recomenda-se atualizar o dashboard primeiro para que os novos recursos do modulo funcionem corretamente.
Instalar mesmo assim
+ Plugins, modelos e trechos para o editor
+ Uma galeria offline de complementos para o Code on the Go: plugins, modelos, trechos e ações de código. Navegue, filtre e baixe sem conexão com a internet.
+ Complementos
+ Atualizar complementos
+ Atualizando complementos...
+ Cancelando...
+ Atualização de complementos cancelada
+ Complementos atualizados
+ Não foi possível atualizar os complementos
+ Alguns complementos não puderam ser atualizados
diff --git a/controller/app/src/main/res/values-ro/strings.xml b/controller/app/src/main/res/values-ro/strings.xml
index 13cda9f7..b2e6959d 100644
--- a/controller/app/src/main/res/values-ro/strings.xml
+++ b/controller/app/src/main/res/values-ro/strings.xml
@@ -1375,4 +1375,14 @@
Este disponibila o actualizare a dashboardului
Se recomanda actualizarea dashboardului mai intai, pentru ca noile functii ale modulului sa functioneze corect.
Instaleaza oricum
+ Pluginuri, șabloane și fragmente pentru editor
+ O galerie offline de suplimente pentru Code on the Go: pluginuri, șabloane, fragmente și acțiuni de cod. Răsfoiește-le, filtrează-le și descarcă-le fără conexiune la internet.
+ Suplimente
+ Actualizează suplimentele
+ Se actualizează suplimentele...
+ Se anulează...
+ Actualizarea suplimentelor a fost anulată
+ Suplimente actualizate
+ Suplimentele nu au putut fi actualizate
+ Unele suplimente nu au putut fi actualizate
diff --git a/controller/app/src/main/res/values-ru-rRU/strings.xml b/controller/app/src/main/res/values-ru-rRU/strings.xml
index 5ece3ef1..1c1a2b82 100644
--- a/controller/app/src/main/res/values-ru-rRU/strings.xml
+++ b/controller/app/src/main/res/values-ru-rRU/strings.xml
@@ -1451,4 +1451,14 @@
Доступно обновление dashboard
Рекомендуется сначала обновить dashboard, чтобы новые функции модуля работали правильно.
Всё равно установить
+ Плагины, шаблоны и фрагменты для редактора
+ Офлайн-галерея дополнений для Code on the Go: плагины, шаблоны, фрагменты и действия с кодом. Просматривайте, фильтруйте и загружайте их без подключения к интернету.
+ Дополнения
+ Обновить дополнения
+ Обновление дополнений...
+ Отмена...
+ Обновление дополнений отменено
+ Дополнения обновлены
+ Не удалось обновить дополнения
+ Некоторые дополнения не удалось обновить
diff --git a/controller/app/src/main/res/values-sk/strings.xml b/controller/app/src/main/res/values-sk/strings.xml
index ab4af362..0c7108fc 100644
--- a/controller/app/src/main/res/values-sk/strings.xml
+++ b/controller/app/src/main/res/values-sk/strings.xml
@@ -1382,4 +1382,14 @@
Je dostupna aktualizacia dashboardu
Odporucame najprv aktualizovat dashboard, aby nove funkcie modulu fungovali spravne.
Aj tak nainstalovat
+ Doplnky, šablóny a úryvky pre editor
+ Offline galéria doplnkov pre Code on the Go: doplnky, šablóny, úryvky a akcie kódu. Prehliadajte, filtrujte a sťahujte ich bez pripojenia na internet.
+ Doplnky
+ Aktualizovať doplnky
+ Aktualizácia doplnkov...
+ Ruší sa...
+ Aktualizácia doplnkov zrušená
+ Doplnky aktualizované
+ Doplnky sa nepodarilo aktualizovať
+ Niektoré doplnky sa nepodarilo aktualizovať
diff --git a/controller/app/src/main/res/values-sr/strings.xml b/controller/app/src/main/res/values-sr/strings.xml
index c9e43fa7..7cd9bf11 100644
--- a/controller/app/src/main/res/values-sr/strings.xml
+++ b/controller/app/src/main/res/values-sr/strings.xml
@@ -1388,4 +1388,14 @@
Доступно је ажурирање dashboard-а
Препоручује се да прво ажурирате dashboard како би нове функције модула радиле исправно.
Ипак инсталирај
+ Додаци, шаблони и исечци за уређивач
+ Офлајн галерија додатака за Code on the Go: додаци, шаблони, исечци и радње над кодом. Прегледајте, филтрирајте и преузимајте без интернет везе.
+ Додаци
+ Ажурирај додатке
+ Ажурирање додатака...
+ Отказивање...
+ Ажурирање додатака је отказано
+ Додаци су ажурирани
+ Није могуће ажурирати додатке
+ Неки додаци нису могли бити ажурирани
diff --git a/controller/app/src/main/res/values-sw/strings.xml b/controller/app/src/main/res/values-sw/strings.xml
index c19a1f7b..fa01f31c 100644
--- a/controller/app/src/main/res/values-sw/strings.xml
+++ b/controller/app/src/main/res/values-sw/strings.xml
@@ -1395,4 +1395,14 @@
Sasisho la dashboard linapatikana
Inashauriwa kusasisha dashboard kwanza ili vipengele vipya vya moduli vifanye kazi vizuri.
Sakinisha hata hivyo
+ Programu-jalizi, violezo na vijisehemu kwa kihariri
+ Ghala la nje ya mtandao la viongezi vya Code on the Go: programu-jalizi, violezo, vijisehemu na vitendo vya msimbo. Vinjari, chuja na upakue bila muunganisho wa intaneti.
+ Viongezi
+ Sasisha viongezi
+ Inasasisha viongezi...
+ Inaghairi...
+ Usasishaji wa viongezi umeghairiwa
+ Viongezi vimesasishwa
+ Imeshindwa kusasisha viongezi
+ Baadhi ya viongezi havikuweza kusasishwa
diff --git a/controller/app/src/main/res/values-ta/strings.xml b/controller/app/src/main/res/values-ta/strings.xml
index 6c5c0fbd..91c8f08c 100644
--- a/controller/app/src/main/res/values-ta/strings.xml
+++ b/controller/app/src/main/res/values-ta/strings.xml
@@ -1395,4 +1395,14 @@
dashboard புதுப்பிப்பு உள்ளது
தொகுதியின் புதிய அம்சங்கள் சரியாக இயங்க முதலில் dashboard ஐப் புதுப்பிக்க பரிந்துரைக்கப்படுகிறது.
இருப்பினும் நிறுவு
+ எடிட்டருக்கான செருகுநிரல்கள், டெம்ப்ளேட்டுகள் மற்றும் துணுக்குகள்
+ Code on the Go-க்கான ஆஃப்லைன் ஆட்-ஆன்கள் தொகுப்பு: செருகுநிரல்கள், டெம்ப்ளேட்டுகள், துணுக்குகள் மற்றும் குறியீட்டுச் செயல்கள். இணைய இணைப்பு இல்லாமல் உலாவவும், வடிகட்டவும், பதிவிறக்கவும்.
+ ஆட்-ஆன்கள்
+ ஆட்-ஆன்களைப் புதுப்பிக்கவும்
+ ஆட்-ஆன்கள் புதுப்பிக்கப்படுகின்றன...
+ ரத்து செய்யப்படுகிறது...
+ ஆட்-ஆன் புதுப்பிப்பு ரத்து செய்யப்பட்டது
+ ஆட்-ஆன்கள் புதுப்பிக்கப்பட்டன
+ ஆட்-ஆன்களைப் புதுப்பிக்க முடியவில்லை
+ சில ஆட்-ஆன்களைப் புதுப்பிக்க முடியவில்லை
diff --git a/controller/app/src/main/res/values-tr/strings.xml b/controller/app/src/main/res/values-tr/strings.xml
index 59e704ae..4a027207 100644
--- a/controller/app/src/main/res/values-tr/strings.xml
+++ b/controller/app/src/main/res/values-tr/strings.xml
@@ -1375,4 +1375,14 @@
Bir dashboard guncellemesi mevcut
Yeni modul ozelliklerinin duzgun calismasi icin once dashboard\'u guncellemeniz onerilir.
Yine de yukle
+ Düzenleyici için eklentiler, şablonlar ve kod parçacıkları
+ Code on the Go için çevrimdışı eklenti galerisi: eklentiler, şablonlar, kod parçacıkları ve kod eylemleri. İnternet bağlantısı olmadan göz atın, filtreleyin ve indirin.
+ Eklentiler
+ Eklentileri güncelle
+ Eklentiler güncelleniyor...
+ İptal ediliyor...
+ Eklenti güncellemesi iptal edildi
+ Eklentiler güncellendi
+ Eklentiler güncellenemedi
+ Bazı eklentiler güncellenemedi
diff --git a/controller/app/src/main/res/values-uk/strings.xml b/controller/app/src/main/res/values-uk/strings.xml
index 975cd750..885bede9 100644
--- a/controller/app/src/main/res/values-uk/strings.xml
+++ b/controller/app/src/main/res/values-uk/strings.xml
@@ -1375,4 +1375,14 @@
Доступне оновлення dashboard
Рекомендовано спочатку оновити dashboard, щоб нові функції модуля працювали правильно.
Усе одно встановити
+ Плагіни, шаблони та фрагменти для редактора
+ Офлайн-галерея додатків для Code on the Go: плагіни, шаблони, фрагменти та дії з кодом. Переглядайте, фільтруйте та завантажуйте їх без підключення до інтернету.
+ Додатки
+ Оновити додатки
+ Оновлення додатків...
+ Скасування...
+ Оновлення додатків скасовано
+ Додатки оновлено
+ Не вдалося оновити додатки
+ Деякі додатки не вдалося оновити
diff --git a/controller/app/src/main/res/values-vi/strings.xml b/controller/app/src/main/res/values-vi/strings.xml
index 631f8c13..77bf474b 100644
--- a/controller/app/src/main/res/values-vi/strings.xml
+++ b/controller/app/src/main/res/values-vi/strings.xml
@@ -1375,4 +1375,14 @@
Da co ban cap nhat dashboard
Nen cap nhat dashboard truoc de cac tinh nang moi cua mo-dun hoat dong dung.
Van cai dat
+ Plugin, mẫu và đoạn mã cho trình chỉnh sửa
+ Thư viện ngoại tuyến các tiện ích bổ sung cho Code on the Go: plugin, mẫu, đoạn mã và thao tác mã. Duyệt, lọc và tải xuống mà không cần kết nối internet.
+ Tiện ích bổ sung
+ Cập nhật tiện ích bổ sung
+ Đang cập nhật tiện ích bổ sung...
+ Đang hủy...
+ Đã hủy cập nhật tiện ích bổ sung
+ Đã cập nhật tiện ích bổ sung
+ Không thể cập nhật tiện ích bổ sung
+ Một số tiện ích bổ sung không thể cập nhật
diff --git a/controller/app/src/main/res/values-yo/strings.xml b/controller/app/src/main/res/values-yo/strings.xml
index deaf4e43..06b0148c 100644
--- a/controller/app/src/main/res/values-yo/strings.xml
+++ b/controller/app/src/main/res/values-yo/strings.xml
@@ -1395,4 +1395,14 @@
Imudojuiwon dashboard wa
A gba o nimoran lati kokoo se imudojuiwon dashboard ki awon eya tuntun ti modulu le sise daradara.
Fi sori ero lonakona
+ Àwọn afikún, àwòṣe, àti àwọn àjákù fún olóòtú
+ Àkójọ àìlẹ́rọ-àyélujára ti àwọn afikún fún Code on the Go: àwọn afikún, àwòṣe, àjákù, àti àwọn ìṣe kóòdù. Ṣàwárí, sẹ́, kí o sì gbà wọ́n sílẹ̀ láìsí ìsopọ̀ Íńtánẹ́ẹ̀tì.
+ Àwọn afikún
+ Ṣe ìmúdójúìwọn àwọn afikún
+ Ń ṣe ìmúdójúìwọn àwọn afikún...
+ Ń fagilé...
+ A fagilé ìmúdójúìwọn àwọn afikún
+ A ṣe ìmúdójúìwọn àwọn afikún
+ A kò lè ṣe ìmúdójúìwọn àwọn afikún
+ Àwọn afikún kan kò lè ṣe ìmúdójúìwọn
diff --git a/controller/app/src/main/res/values-zh-rCN/strings.xml b/controller/app/src/main/res/values-zh-rCN/strings.xml
index 452a94e6..64c3c914 100644
--- a/controller/app/src/main/res/values-zh-rCN/strings.xml
+++ b/controller/app/src/main/res/values-zh-rCN/strings.xml
@@ -1376,4 +1376,14 @@
有可用的 dashboard 更新
建议先更新 dashboard,以便模块的新功能正常运行。
仍然安装
+ 编辑器的插件、模板和代码片段
+ Code on the Go 的离线附加组件库:插件、模板、代码片段和代码操作。无需联网即可浏览、筛选和下载。
+ 附加组件
+ 更新附加组件
+ 正在更新附加组件...
+ 正在取消...
+ 已取消附加组件更新
+ 附加组件已更新
+ 无法更新附加组件
+ 部分附加组件无法更新
diff --git a/controller/app/src/main/res/values/strings.xml b/controller/app/src/main/res/values/strings.xml
index 77929288..ad972cc9 100644
--- a/controller/app/src/main/res/values/strings.xml
+++ b/controller/app/src/main/res/values/strings.xml
@@ -1560,4 +1560,17 @@
A dashboard update is available
Updating the dashboard first is recommended so new module features work correctly.
Install anyway
+ Code on the Go Add-ons
+ Code on the Go Add-ons
+ ~600 MB
+ Plugins, templates, and snippets for the editor
+ An offline gallery of add-ons for Code on the Go: plugins, templates, snippets, and code actions. Browse, filter, and download them with no internet connection.
+ Add-ons
+ Update add-ons
+ Updating add-ons...
+ Cancelling...
+ Add-ons update cancelled
+ Add-ons updated
+ Could not update the add-ons
+ Some add-ons could not be updated
diff --git a/controller/docs/ADR-addons-offline-gallery.md b/controller/docs/ADR-addons-offline-gallery.md
new file mode 100644
index 00000000..0f88494c
--- /dev/null
+++ b/controller/docs/ADR-addons-offline-gallery.md
@@ -0,0 +1,150 @@
+# ADR: Add-ons offline gallery as a static-content role
+
+Status: Accepted
+Ticket: K2GO-99
+Date: 2026-10-01
+
+## Context
+
+Code on the Go (COGO) add-ons are plugins packaged as `.cgp` files. They are distributed as a
+static gallery on Cloudflare R2 at `https://addons.appdevforall.org`, produced by the
+`appdevforall/addons` "garden" (the `tools/addons` Python tool and the `publish-addons.yml`
+workflow). The garden builds each `.cgp`, generates a catalog, and uploads a complete static site.
+
+A K2Go box is offline. People on the box need to browse, filter, read, and download the add-ons
+without internet, and (later) install them in COGO. So the box must host a local copy of that
+gallery.
+
+The published site is a client-rendered static site:
+
+- `index.html` plus content-hashed shell assets under `assets/` (`styles..css`,
+ `app..js`, `adfa-logo..svg`).
+- `v1/catalog.json` (and `v1/catalog.schema.json`).
+- Per add-on: `dl/.cgp`, `p/.html`, `p/.png`, `p/-night.png`,
+ `src/-src.tar.gz`.
+
+`app.js` reads `v1/catalog.json` at runtime and renders the cards, descriptions, and filtering on
+the client. So a faithful mirror of these files is a fully navigable, filterable gallery, not a bare
+list of links.
+
+This is the first of a family of content repo roles (add-ons now, maven-offline next). The goal is
+cheap, localized additions that reuse existing patterns and do not grow a god class.
+
+## Decision
+
+Add a new static-distribution Ansible role, shaped on `roles/code`, that mirrors the published
+add-ons gallery from R2 into the rootfs and serves it at `http://box:8085/code-addons/`. Refresh a
+deployed box through dash-node (the live-content pattern), not through runrole.
+
+### 1. Evergreen, reference-driven mirror (no fixed file list)
+
+The mirror follows references from two stable anchors, so it never carries a hardcoded list of
+files:
+
+- `v1/catalog.json` enumerates every per-add-on file for every add-on: `download`, `iconUrl`,
+ `iconDarkUrl`, `pageUrl`, `sourceTarball`, each with `sha256` and `size`. This is the part that
+ grows (21 add-ons today, more tomorrow); it lists itself in full.
+- `index.html` names its own content-hashed shell assets in its `link`/`script`/`img` tags. Parse
+ it, follow the references.
+
+New add-ons and changed or added shell assets are picked up automatically. The only hard-coded names
+are the two anchors, which are the site's permanent contract: the catalog is versioned under `v1/`,
+so a breaking change to its shape becomes `v2/`, a deliberate signaled change, not silent drift. A
+file that is reachable from neither anchor is a file the browser never requests either, so it is not
+part of the functioning site.
+
+### 2. One transform only: the catalog base
+
+The shell and the per-add-on pages use relative asset paths (`index.html` uses `assets/...`;
+`p/.html` uses `../assets/...`), so they serve unchanged at the `/code-addons/` sub-path. The
+only rewrite is the catalog base: replace `https://addons.appdevforall.org` with `/code-addons` in
+`catalog.json`. `app.js` resolves catalog URLs against `location.origin`, so the catalog base must be
+a root-absolute path (`/code-addons`) that carries the mount. It then renders as
+`http://:8085/code-addons/...` on any box, with no baked IP.
+
+The external marketing links and the Google Fonts stylesheet stay absolute; offline they degrade
+without affecting the add-on gallery (the font falls back to the system stack; the nav links do not
+open).
+
+### 3. Integrity from the catalog
+
+Each `.cgp` is verified with the `sha256` the catalog already carries (Ansible `get_url`
+`checksum:`), so no custom verification is written.
+
+### 4. Install is the role; update is dash-node
+
+- Install (bake time): the role populates the mirror during the rootfs build, where CI has internet.
+ A fresh box ships with the add-ons.
+- Update (deployed box): dash-node re-runs the same reference crawl in-server, durable and resumable,
+ the ZIM / ADFA-4849 live-content pattern. It is surfaced as an "Update add-ons" action, like
+ Forgejo's "Update repos". No runrole is needed: the content is pure static files that dash-node can
+ replace live. The role (Ansible) and the dash-node job share the algorithm, documented here, not
+ code, the same split Forgejo uses.
+
+### 5. Arch-portable by construction
+
+The content is pure static files: no binary, no architecture-specific anything. There is no
+per-architecture branch. The only platform branch is the nginx reload (`systemd reload` vs
+`pdsm restart nginx`), inherited from `roles/code`. The role runs identically on proot, systemd,
+ARM64, and AMD64.
+
+### Filesystem and serving
+
+- Mirror into `{{ content_base }}/www/code-addons` (`/library/www/code-addons`), preserving the
+ published layout: `index.html`, `assets/`, `v1/`, `p/`, `dl/`, `src/`.
+- nginx: a static `code-addons-nginx.conf` location block, copied like `code-nginx.conf`, served at
+ `/code-addons/` on the IIAB nginx (port 8085 under proot).
+- Record disk usage and `addons_installed` in `iiab.ini` / `iiab_state.yml`, like every role.
+
+### How the role is added (upstream-patches)
+
+The role ships as a new `tools/upstream-patches` patch, the same mechanism as
+`0003-forgejo-add-role-4505.patch`: it adds `roles//*` plus the variable wiring
+(`default_vars.yml`, `local_vars_android_{small,medium,large}.yml`, `local_vars_large.yml`), the
+`0-init` validations, and the `6-generic-apps` include. It ships `install`/`enabled` False by
+default; the K2Go bake flips them per tier.
+
+### Load-bearing guards (what must not regress)
+
+- One source per fact. The catalog is the single source of the add-on set; no second list is kept.
+ The serve path `/code-addons` is defined once and reused by the role variable, the catalog base
+ rewrite, and the nginx location.
+- The mirror is reference-driven, never a hardcoded enumeration, so a structure change cannot
+ silently drop a file.
+- Update lifecycle. dash-node writes the new mirror to a temporary directory and swaps it in, so a
+ refresh interrupted mid-download never serves a half-mirror. The dash-node change names the owner
+ of that swap.
+
+## Consequences
+
+- Hosting the add-ons gallery offline is a small static-content role plus a dash-node refresh job,
+ reusing `roles/code` and the live-content pattern. No new framework.
+- The gallery stays evergreen: each mirror pulls the current shell and the current catalog, coherent
+ with each other.
+- The next content role (maven-offline) is this role with a different base and anchors.
+
+## Alternatives considered
+
+- Clone the add-ons repo and prune to what is needed. Rejected. The repo holds the gallery source
+ and the plugin source, not the built `.cgp` or `catalog.json` (those exist only on R2), so a clone
+ still needs R2 for the content and mixes two sources that can drift. It also drags submodules
+ (`llama.cpp`) and Gradle. The reference-driven R2 mirror is cleaner and coherent.
+- A hardcoded file list in the role. Rejected. It drifts silently when the site structure changes (a
+ new asset, a renamed file).
+- Map `addons.appdevforall.org` to the box (dnsmasq plus nginx `server_name`) and serve the catalog
+ unchanged. Viable and needs zero rewrite, but the chosen design uses an explicit local path
+ (`/code-addons`) and does not shadow the public hostname. Kept as a fallback if COGO hardcodes its
+ base.
+- Update via `runrole --reinstall` (like Forgejo). Unnecessary here. The content is static, so
+ dash-node replaces files live without Ansible.
+
+## Open items
+
+- COGO in-app install. Whether the COGO Plugin Manager can be pointed at
+ `http://box:8085/code-addons/v1/catalog.json` to install directly, or people download the `.cgp`
+ from the gallery and sideload it. The gallery works either way; this decides whether a COGO setting
+ is also wired. Needs a look at the CodeOnTheGo app.
+- Device smoke. Confirm the nginx root/alias and that `app.js` `fetch("v1/catalog.json")` resolves
+ under the `/code-addons/` mount on a real device.
+- Page-body images. If an authored per-add-on page body references an image that the garden does not
+ publish, it is already broken upstream; out of scope here.
diff --git a/iiab-android b/iiab-android
index bb8fee36..5252996f 100644
--- a/iiab-android
+++ b/iiab-android
@@ -644,6 +644,20 @@ else
echo "forgejo_enabled: True" >> "$LOCAL_VARS_DEST"
;;
esac
+
+ # K2GO-99: the code_addons role (offline Code on the Go add-ons gallery) ships install/enabled
+ # False in every vars template (carried via tools/upstream-patches), so enable it for Standard/Full
+ # here, not Basic: the mirror pulls a large set of .cgp files (hundreds of MB).
+ case "$LOCAL_VARS_URL" in
+ *_small*)
+ log "Basic tier: Code on the Go add-ons left off."
+ ;;
+ *)
+ sed -i -E '/^[[:space:]]*code_addons_(install|enabled)[[:space:]]*:/d' "$LOCAL_VARS_DEST"
+ echo "code_addons_install: True" >> "$LOCAL_VARS_DEST"
+ echo "code_addons_enabled: True" >> "$LOCAL_VARS_DEST"
+ ;;
+ esac
fi
#-----------------------------
diff --git a/static/dashboard/CHANGELOG.md b/static/dashboard/CHANGELOG.md
index b74de345..e6b978ee 100644
--- a/static/dashboard/CHANGELOG.md
+++ b/static/dashboard/CHANGELOG.md
@@ -4,6 +4,7 @@ One line per version, newest first. Every REST-facing change bumps the version i
(the app surfaces it via `/system/dashboard/update-check` and the "Update available" pill), so this
file is the human record of what each bump enables. Keep entries short: `version - change (TICKET)`.
+- **1.3.8** - Add-ons offline gallery refresh (K2GO-99). New `POST /addons/refresh` re-mirrors the published Code on the Go add-ons gallery into `/library/www/code-addons` LIVE (box up, no runrole) through a detached wrapper (`tools/code-addons-refresh.sh`) that mirrors into a staging dir and swaps it in only on success (a failed or cancelled refresh never serves a half-mirror), `GET /addons/refresh/status` returns `{state, lines, downloaded, failed}` to poll (downloaded/failed parsed from the mirror's final log line), and `POST /addons/refresh/cancel` stops a running refresh (SIGKILL to the wrapper process group; safe because the live gallery is only replaced after a clean run). The wrapper reuses the role's `mirror_addons.py` (reference-driven mirror, sha256 verify, Cloudflare strip, catalog base rewrite), so install (bake) and refresh (live) share one mechanism. Localhost-only. (K2GO-99)
- **1.3.7** - Non-destructive live repo refresh for Forgejo (K2GO-422). New `POST /forgejo/refresh` runs the box orchestration (`refresh_forgejo`) LIVE (box up, no runrole, no stop) through a detached wrapper (`tools/forgejo-refresh.sh`), and `GET /forgejo/refresh/status` returns `{state, lines}` to poll, and `POST /forgejo/refresh/cancel` stops a running refresh (SIGKILL to the wrapper process group; safe because the refresh only ever fast-forwards or writes a side ref). Per seeded example repo it fast-forwards when pristine, does a clean 3-way merge (`git merge-tree`, no worktree) when the owner committed on the served branch, and skips on conflict; the branch only moves forward (every push is a fast-forward through the forge hooks), so a refresh never drops a commit. It also writes a human status board into each repo description (Last Update / Last Attempt / Status), derived from git state. Localhost-only. (K2GO-422)
- **1.3.6** - Read-only Forgejo status for the post-install repos action (K2GO-422). New `GET /forgejo/status` returns `{reachable, adminExists, adminAuthenticable, repos, manageable}` from in-process HTTP to the forge API (no CLI, no fork, so it answers even when dash-node is off-proot, unlike the seed). The app uses it to offer "Install repos" (manageable and no repos yet), "Update repos" (repos present), or block (a K2Go admin exists that we cannot authenticate: password changed, so we must not guess or clobber). `manageable` = we authenticate as the admin OR the admin is absent (the seed can create it). Read-only, localhost-only. (K2GO-422)
- **1.3.5** - Forgejo repo seed via dash-node (K2GO-417). New `POST /forgejo/seed {includeRepos}` runs the box orchestration (`seed_forgejo`) LIVE (box up, no runrole, no stop) through a detached wrapper (`tools/forgejo-seed.sh`), and `GET /forgejo/seed/status` returns `{state, lines}` for the app to poll. Seeds the admin + org always, plus the full example repo set when includeRepos (default true; false = admin/org only). The forge stays a conventional role (runrole); this only injects content, so it is also the base for live repo updates (K2GO-422). Localhost-only. (K2GO-417)
diff --git a/static/dashboard/package.json b/static/dashboard/package.json
index 6aec73ca..89a1d17f 100644
--- a/static/dashboard/package.json
+++ b/static/dashboard/package.json
@@ -1,6 +1,6 @@
{
"name": "dashboard-console",
- "version": "1.3.7",
+ "version": "1.3.8",
"description": "",
"main": "index.js",
"scripts": {
diff --git a/static/dashboard/routes.ts b/static/dashboard/routes.ts
index da167b07..6e02b1e1 100644
--- a/static/dashboard/routes.ts
+++ b/static/dashboard/routes.ts
@@ -447,6 +447,77 @@ apiRouter.post('/forgejo/refresh/cancel', (_req: Request, res: Response): void =
res.json({ ok: true, state: 'cancelled' });
});
+// --- Add-ons offline gallery refresh (K2GO-99) ------------------------------
+// Re-mirror the published Code on the Go add-ons gallery into /library/www/code-addons
+// LIVE (box up, no runrole). Same detached-script plus status/log pattern as the Forgejo
+// refresh. The wrapper mirrors into a staging dir and swaps it in only on success, so a
+// failed or cancelled refresh never replaces the live gallery with a half-mirror. The
+// wrapper reuses the role's mirror_addons.py, so install (bake) and refresh (live) share
+// one mechanism. Localhost-only.
+const ADDONS_REFRESH_SCRIPT = '/opt/iiab-android/tools/code-addons-refresh.sh';
+const ADDONS_REFRESH_STATUS = '/var/run/code-addons-refresh.status';
+const ADDONS_REFRESH_LOG = '/var/log/code-addons-refresh.log';
+const ADDONS_REFRESH_PID = '/var/run/code-addons-refresh.pid';
+const ADDONS_REFRESH_LOG_TAIL = 200;
+
+apiRouter.post('/addons/refresh', (_req: Request, res: Response): void => {
+ let running = false;
+ try { running = fs.readFileSync(ADDONS_REFRESH_STATUS, 'utf8').trim() === 'running'; } catch { /* none */ }
+ if (running) { res.status(409).json({ error: 'an add-ons refresh is already running' }); return; }
+ if (!fs.existsSync(ADDONS_REFRESH_SCRIPT)) { res.status(500).json({ error: 'add-ons refresh script not found' }); return; }
+ try {
+ // setsid => own session, so a pdsm restart of dash-node cannot kill the refresh mid-run.
+ const child = spawn('setsid', ['bash', ADDONS_REFRESH_SCRIPT], {
+ detached: true, stdio: 'ignore', env: { ...process.env },
+ });
+ child.unref();
+ // Mark running synchronously before answering, so an immediate poll cannot read a stale state.
+ try { fs.writeFileSync(ADDONS_REFRESH_STATUS, 'running'); } catch { /* best effort */ }
+ res.status(202).json({ ok: true, state: 'running' });
+ } catch (e: any) {
+ res.status(500).json({ error: e?.message || 'could not start add-ons refresh' });
+ }
+});
+
+apiRouter.get('/addons/refresh/status', (_req: Request, res: Response): void => {
+ res.set('Cache-Control', 'no-store');
+ let state = 'idle';
+ try { state = fs.readFileSync(ADDONS_REFRESH_STATUS, 'utf8').trim() || 'idle'; } catch { /* no file yet */ }
+ let lines: string[] = [];
+ try {
+ const all = fs.readFileSync(ADDONS_REFRESH_LOG, 'utf8').split('\n');
+ if (all.length && all[all.length - 1] === '') all.pop();
+ lines = all.slice(-ADDONS_REFRESH_LOG_TAIL);
+ } catch { /* no log yet */ }
+ // K2GO-99: mirror_addons.py ends with "done: N downloaded, M head-checked, K failed".
+ // Surface downloaded/failed so the app reports the outcome without parsing the whole log.
+ let downloaded = 0;
+ let failed = 0;
+ for (const l of lines) {
+ const m = l.match(/^done: (\d+) downloaded, \d+ head-checked, (\d+) failed/);
+ if (m) { downloaded = parseInt(m[1], 10); failed = parseInt(m[2], 10); }
+ }
+ res.json({ state, lines, downloaded, failed });
+});
+
+// Cancel a running refresh. The wrapper runs under setsid (its own process group), so a SIGKILL to
+// the group (kill -pid) stops the shell and the mirror child. Safe: the live gallery is only replaced
+// after a clean run, so a mid-run kill leaves the served gallery untouched (the partial staging dir is
+// cleared on the next refresh). No-op if not running.
+apiRouter.post('/addons/refresh/cancel', (_req: Request, res: Response): void => {
+ let running = false;
+ try { running = fs.readFileSync(ADDONS_REFRESH_STATUS, 'utf8').trim() === 'running'; } catch { /* none */ }
+ if (!running) { res.json({ ok: true, state: 'idle' }); return; }
+ let pid = 0;
+ try { pid = parseInt(fs.readFileSync(ADDONS_REFRESH_PID, 'utf8').trim(), 10); } catch { /* no pid */ }
+ if (pid > 0) {
+ try { process.kill(-pid, 'SIGKILL'); } catch { /* already gone */ }
+ }
+ // Mark terminal ourselves: the killed wrapper cannot write its own status.
+ try { fs.writeFileSync(ADDONS_REFRESH_STATUS, 'cancelled'); } catch { /* best effort */ }
+ res.json({ ok: true, state: 'cancelled' });
+});
+
// Trigger a rebuild. Fire-and-forget: launches the orchestrator DETACHED and returns 202 at once;
// the app then polls /system/version + RestReadiness until the API is back on the new version.
// ADFA-5339: an optional { site: true } also refreshes the served landing page in the same run. The
diff --git a/tools/code-addons-refresh.sh b/tools/code-addons-refresh.sh
new file mode 100644
index 00000000..3c34455c
--- /dev/null
+++ b/tools/code-addons-refresh.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+# ============================================================================
+# Name : tools/code-addons-refresh.sh
+# Author : AppDevForAll
+# Copyright : Copyright (c) 2026 AppDevForAll
+# Description : K2GO-99. dash-node-driven refresh of the offline Code on the Go
+# add-ons gallery. Re-mirrors the published gallery into a staging
+# directory and swaps it in on success, LIVE (box up, no runrole).
+# Writes a status file plus a log that the dash-node endpoint polls.
+# Mirrors tools/forgejo-refresh.sh (status file plus detached run).
+#
+# Safe: the mirror runs into ".new"; the live gallery is only replaced
+# after a clean run, so a failed or cancelled refresh never serves a half-mirror.
+# ============================================================================
+set -u
+STATUS=/var/run/code-addons-refresh.status
+LOG=/var/log/code-addons-refresh.log
+PID=/var/run/code-addons-refresh.pid
+
+# SERVE must match code_addons_serve_path (roles/code_addons/defaults). The source
+# base and the catalog serve-base come from mirror_addons.py's own defaults, so
+# they are not restated here.
+SERVE=/library/www/code-addons
+MIRROR=/opt/iiab/iiab/roles/code_addons/files/mirror_addons.py
+
+: > "$LOG" 2>/dev/null || true
+echo running > "$STATUS" 2>/dev/null || true
+# dash-node spawns this with setsid, so this shell leads the process group. Record its
+# PID so POST /addons/refresh/cancel can stop the whole group (kill -PID) mid-run.
+echo $$ > "$PID" 2>/dev/null || true
+# Drop the pid file on a normal exit, so a finished run leaves no stale pid for a later
+# cancel to kill (a reused pid). A SIGKILL from cancel skips this, but cancel also sets
+# the status to 'cancelled', which guards against that.
+trap 'rm -f "$PID" 2>/dev/null' EXIT
+
+{
+ if [ ! -f "$MIRROR" ]; then
+ echo "code-addons-refresh: mirror script not found at $MIRROR"
+ echo error > "$STATUS" 2>/dev/null || true
+ exit 1
+ fi
+
+ STAGE="${SERVE}.new"
+ OLD="${SERVE}.old"
+ rm -rf "$STAGE" "$OLD" 2>/dev/null || true
+
+ # Mirror into the staging dir. mirror_addons.py follows references from index.html and
+ # v1/catalog.json, verifies each .cgp by sha256, strips the Cloudflare injections, and
+ # rewrites the catalog base. A non-zero exit means a download or checksum failed.
+ if ! python3 "$MIRROR" --out "$STAGE"; then
+ echo "code-addons-refresh: mirror failed; live gallery left untouched"
+ rm -rf "$STAGE" 2>/dev/null || true
+ echo error > "$STATUS" 2>/dev/null || true
+ exit 1
+ fi
+
+ # Swap the fresh mirror in. All three paths live under /library/www, so each mv is a
+ # same-filesystem rename; the window where is absent is one rename wide, and
+ # nginx serves either the old tree or the new one, never a partial.
+ if [ -d "$SERVE" ]; then mv "$SERVE" "$OLD" 2>/dev/null || true; fi
+ if mv "$STAGE" "$SERVE" 2>/dev/null; then
+ rm -rf "$OLD" 2>/dev/null || true
+ echo "code-addons-refresh: swapped in the fresh gallery"
+ echo done > "$STATUS" 2>/dev/null || true
+ else
+ # Could not move the new tree into place: restore the previous one.
+ [ -d "$OLD" ] && mv "$OLD" "$SERVE" 2>/dev/null || true
+ rm -rf "$STAGE" 2>/dev/null || true
+ echo "code-addons-refresh: swap failed; restored the previous gallery"
+ echo error > "$STATUS" 2>/dev/null || true
+ exit 1
+ fi
+} >> "$LOG" 2>&1
diff --git a/tools/upstream-patches/patches/0004-code-addons-add-role.patch b/tools/upstream-patches/patches/0004-code-addons-add-role.patch
new file mode 100644
index 00000000..295bb8b8
--- /dev/null
+++ b/tools/upstream-patches/patches/0004-code-addons-add-role.patch
@@ -0,0 +1,538 @@
+Upstream-PR: not submitted (K2Go-specific: mirrors the Code on the Go add-ons gallery at addons.appdevforall.org)
+Upstream-Status: carried (K2Go-specific role; no upstream iiab/iiab PR planned)
+Applies-to: roles/code_addons/* (new role); vars/default_vars.yml (code_addons_install/enabled); roles/6-generic-apps/tasks/main.yml (role include)
+Summary: Add the 'code_addons' role: an offline mirror of the Code on the Go add-ons gallery (plugins, templates, snippets, code-actions), served by nginx at http://box/code-addons. Static files only, no daemon. The role runs files/mirror_addons.py, which follows references from index.html and v1/catalog.json, downloads and sha256-verifies every add-on file, strips the Cloudflare injections, and rewrites the catalog base to the local path. Update a deployed box via dash-node. K2GO-99. See controller/docs/ADR-addons-offline-gallery.md.
+
+diff --git a/roles/6-generic-apps/tasks/main.yml b/roles/6-generic-apps/tasks/main.yml
+index 6705477..b940fca 100644
+--- a/roles/6-generic-apps/tasks/main.yml
++++ b/roles/6-generic-apps/tasks/main.yml
+@@ -62,6 +62,11 @@
+ name: wordpress
+ when: wordpress_install and not is_proot
+
++- name: CODE_ADDONS
++ include_role:
++ name: code_addons
++ when: code_addons_install is defined and code_addons_install
++
+ - name: Recording STAGE 6 HAS COMPLETED ====================
+ lineinfile:
+ path: "{{ iiab_env_file }}"
+diff --git a/roles/code_addons/defaults/main.yml b/roles/code_addons/defaults/main.yml
+new file mode 100644
+index 0000000..b8568bf
+--- /dev/null
++++ b/roles/code_addons/defaults/main.yml
+@@ -0,0 +1,22 @@
++# code_addons_install: True
++# code_addons_enabled: True
++
++# Offline mirror of the Code on the Go add-ons gallery (plugins, templates,
++# snippets, code-actions). Sibling of the 'code' role: a static site served by
++# NGINX, no daemon, no database. The gallery is browsed at http://box/code-addons
++# and each add-on downloads from http://box/code-addons/dl/.cgp.
++# Design: controller/docs/ADR-addons-offline-gallery.md (K2GO-99).
++
++# The published gallery to mirror from (the public R2 site). The role follows
++# references from index.html and v1/catalog.json, so it needs no file list: a new
++# add-on or a new shell asset is picked up on the next (re)install.
++# TO UPDATE a deployed box without a re-bake, dash-node re-runs the same mirror
++# in-server; a re-bake or 'runrole --reinstall code_addons' re-mirrors here.
++code_addons_source_base: "https://addons.appdevforall.org"
++
++# Local serve path. The catalog base is rewritten to this, so the gallery works
++# at http:///code-addons/ with no host baked into the files.
++code_addons_url: /code-addons
++
++# Served document root (static files, like roles/code).
++code_addons_serve_path: "{{ content_base }}/www/code-addons" # /library/www/code-addons
+diff --git a/roles/code_addons/files/mirror_addons.py b/roles/code_addons/files/mirror_addons.py
+new file mode 100644
+index 0000000..e1ab8ae
+--- /dev/null
++++ b/roles/code_addons/files/mirror_addons.py
+@@ -0,0 +1,319 @@
++#!/usr/bin/env python3
++"""Mirror the Code on the Go add-ons gallery for offline serving.
++
++Download the published static site (shell, catalog, and per-add-on files) from a
++source base (default: the public R2 site) into a local directory, then rewrite the
++catalog base to the local serve path. The download set is discovered by following
++references from two stable anchors, so it never carries a hardcoded file list:
++
++ - index.html names its own shell assets in its link, script, and img tags.
++ - v1/catalog.json lists every per-add-on file, each with a sha256.
++
++Add-on binaries are verified against the sha256 the catalog carries. The only
++transform is the catalog base rewrite; the shell and the per-add-on pages use
++relative asset paths, so they serve unchanged under the serve path.
++
++This is the shared mechanism for the add-ons offline gallery (K2GO-99). The
++Ansible role calls it at bake time; dash-node re-runs the same steps in-server
++for the online update. See controller/docs/ADR-addons-offline-gallery.md.
++"""
++
++import argparse
++import hashlib
++import json
++import re
++import sys
++import urllib.request
++import urllib.error
++from html.parser import HTMLParser
++from pathlib import Path
++from urllib.parse import urljoin, urlsplit
++
++USER_AGENT = "k2go-addons-mirror/1"
++TIMEOUT = 60
++RETRIES = 3
++
++# Fixed anchors, relative to the source base. Everything else is discovered.
++INDEX = "index.html"
++CATALOG = "v1/catalog.json"
++CATALOG_SCHEMA = "v1/catalog.schema.json"
++
++# Tags whose href/src names a static asset of the site (not a nav link).
++ASSET_TAGS = {"link": "href", "script": "src", "img": "src"}
++
++
++class AssetRefs(HTMLParser):
++ """Collect href/src values from asset tags (link, script, img)."""
++
++ def __init__(self):
++ super().__init__()
++ self.refs = []
++
++ def handle_starttag(self, tag, attrs):
++ attr = ASSET_TAGS.get(tag)
++ if not attr:
++ return
++ for name, value in attrs:
++ if name == attr and value:
++ self.refs.append(value)
++
++
++def is_local_ref(ref):
++ """True for a same-site relative asset path.
++
++ A real site asset is relative with no scheme and no leading slash
++ (for example "assets/app..js" or "../assets/styles..css").
++ Absolute URLs (fonts, analytics) and root-absolute paths (the Cloudflare
++ "/cdn-cgi/..." injections) are not part of the authored site and are left out.
++ """
++ if not ref:
++ return False
++ if urlsplit(ref).scheme:
++ return False
++ if ref.startswith("/"):
++ return False
++ return True
++
++
++# Cloudflare rewrites the served HTML in ways that are not part of the authored
++# site and must not ship on the box:
++# - the analytics beacon script (the box does not track anyone),
++# - the email-decode helper script (a dead "/cdn-cgi/..." reference offline),
++# - an inline JS-challenge / bot-detection script that loads
++# "/cdn-cgi/challenge-platform/...",
++# - the footer mailto, replaced by an obfuscated "/cdn-cgi/l/email-protection"
++# link, and any visible email replaced by a data-cfemail hex blob.
++# Remove every Cloudflare script block (inline or external) and restore the real
++# emails (Cloudflare encodes an address with a one-byte XOR, so it decodes with
++# no network call). The Google Fonts link is left in: it fails gracefully offline
++# and works when the box is online.
++SCRIPT_BLOCK = re.compile(r'