diff --git a/README.md b/README.md
index f65eb44..f580955 100644
--- a/README.md
+++ b/README.md
@@ -339,6 +339,13 @@ curl --unix-socket amduatd.sock \
-H 'X-Amduat-Space: demo'
```
+## Workspace UI
+
+`/workspace` serves a minimal, human-facing page that consumes
+`/v1/space/workspace` and `/v1/space/mounts/sync/until`. It is a convenience
+view for inspection and manual sync control, not a stable API. For
+programmatic use, call the `/v1/*` endpoints directly.
+
## Space mounts sync (track mounts)
`/v1/space/mounts/sync/until` runs the federation pull/until loop for every
@@ -554,6 +561,9 @@ When the daemon uses the `fs` store backend, index-only checks are reported as
- response: `{program_ref}`
- `POST /v1/context_frames`
+UI (human-facing, not an API contract):
+- `GET /workspace` → minimal workspace snapshot + sync controls (uses `/v1/space/workspace`)
+
Receipt example (with v1.1 fields):
```json
diff --git a/src/amduatd.c b/src/amduatd.c
index 1415fac..453c4f6 100644
--- a/src/amduatd.c
+++ b/src/amduatd.c
@@ -133,6 +133,304 @@ static const uint64_t AMDUATD_FED_TICK_MS = 1000u;
static const size_t AMDUATD_FED_INGEST_MAX_BYTES = 8u * 1024u * 1024u;
static const size_t AMDUATD_REF_TEXT_MAX = 256u;
static const size_t AMDUATD_SPACE_MANIFEST_MAX_BYTES = 64u * 1024u;
+static const char k_amduatd_workspace_html[] =
+ "\n"
+ "\n"
+ "
\n"
+ " \n"
+ " \n"
+ " amduatd workspace\n"
+ " \n"
+ "\n"
+ "\n"
+ " \n"
+ "
Workspace snapshot
\n"
+ "
Local, read-only view of the effective space.
\n"
+ "
\n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " syncing...\n"
+ " invalid space id (no \"/\" or \"..\")\n"
+ "
\n"
+ "\n"
+ "
\n"
+ "\n"
+ "
\n"
+ "
\n"
+ "
Effective space
\n"
+ "
-
\n"
+ "
Store backend
\n"
+ "
-
\n"
+ "
Federation
\n"
+ "
-
\n"
+ "
Manifest ref
\n"
+ "
-
\n"
+ "
\n"
+ "
\n"
+ "
Store capabilities
\n"
+ "
-
\n"
+ "
Sync result
\n"
+ "
-
\n"
+ "
\n"
+ " Full sync response
\n"
+ " \n"
+ " \n"
+ "
\n"
+ "
\n"
+ "\n"
+ "
\n"
+ "
Mounts
\n"
+ "
\n"
+ " \n"
+ " \n"
+ " | Name | \n"
+ " Peer | \n"
+ " Remote space | \n"
+ " Mode | \n"
+ " Cursor | \n"
+ " Status | \n"
+ "
\n"
+ " \n"
+ " \n"
+ "
\n"
+ "
\n"
+ "\n"
+ "
\n"
+ "
Raw workspace JSON
\n"
+ "
\n"
+ "
\n"
+ "
\n"
+ "\n"
+ " \n"
+ "\n"
+ "\n";
static const char k_amduatd_contract_v1_json[] =
"{"
"\"contract\":\"AMDUATD/API/1\","
@@ -1848,6 +2146,16 @@ mounts_sync_cleanup:
return ok;
}
+static bool amduatd_handle_get_workspace_ui(int fd) {
+ return amduatd_http_send_status(fd,
+ 200,
+ "OK",
+ "text/html; charset=utf-8",
+ (const uint8_t *)k_amduatd_workspace_html,
+ strlen(k_amduatd_workspace_html),
+ false);
+}
+
static bool amduatd_handle_get_space_workspace(
int fd,
amduat_asl_store_t *store,
@@ -9101,6 +9409,10 @@ static bool amduatd_handle_conn(int fd,
ok = amduatd_handle_meta(fd, cfg, api_contract_ref, false);
goto conn_cleanup;
}
+ if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/workspace") == 0) {
+ ok = amduatd_handle_get_workspace_ui(fd);
+ goto conn_cleanup;
+ }
if (strcmp(req.method, "HEAD") == 0 && strcmp(no_query, "/v1/meta") == 0) {
ok = amduatd_handle_meta(fd, cfg, api_contract_ref, true);
goto conn_cleanup;
diff --git a/src/amduatd_space.c b/src/amduatd_space.c
index 37c1839..0aab475 100644
--- a/src/amduatd_space.c
+++ b/src/amduatd_space.c
@@ -8,24 +8,7 @@
#include
static bool amduatd_space_name_valid(const char *name) {
- size_t i;
- size_t len;
- if (name == NULL) {
- return false;
- }
- len = strlen(name);
- if (len == 0 || len > 64) {
- return false;
- }
- for (i = 0; i < len; ++i) {
- unsigned char c = (unsigned char)name[i];
- if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' ||
- c == '-' || c == '.' || c == '/') {
- continue;
- }
- return false;
- }
- return true;
+ return amduat_asl_pointer_name_is_valid(name);
}
static bool amduatd_space_strdup_cstr(const char *s, char **out) {