From 6859be688e8f64ea207a7fe883281d1d61b95352 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 3 Sep 2026 14:47:09 +0100 Subject: [PATCH 1/4] Add CHANGELOG; switch runbook rollback to tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGELOG.md started (Keep a Changelog style), with the v0.1.0 release notes; versions track the git tags and __version__. - Runbook §7 now deploys a specific tag (git fetch --tags && checkout vX.Y.Z) and rolls back to the previous tag, with a note that rollback reverts code not data (restore the data backup if a migration ran). (The deploy kickoff brief lives in planning/, which is gitignored — it's a local hand-to-the-agent note, not shipped in the repo.) Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++ deploy/cloud_agent_deploy.md | 24 ++++++++++++------ 2 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9ce888e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog + +All notable changes to Daybook are recorded here. Format loosely follows +[Keep a Changelog](https://keepachangelog.com/); versions are SemVer and match +the git tags (`vX.Y.Z`) and `daybook_project.__version__`. + +## [Unreleased] + +_Nothing yet._ + +## [0.1.0] — 2026-09-03 + +First tagged release. + +### Artifacts & rendering +- Artifact kinds: `markdown`, `html_fragment`, `html_full`, `deck`, `json`, + `csv`, `image`, `pdf`, `binary`. +- `.xlsx` uploads convert server-side to a `csv` artifact (active sheet, + saved formula results). +- Markdown code blocks get syntax highlighting; tables (markdown & + html_fragment) render styled — monospace cells, sans-serif headers. +- `json`/`csv` render as an interactive collapsible tree / sortable table + (Grid.js + renderjson, vendored locally). +- Unified off-white document background; system sans-serif body text. + +### API +- `POST/GET/PATCH/DELETE /api/artifacts`, list filters (source/label/day/ + kind/q), `/api/assets` image upload, `/api/capabilities` (reports + `version`). Per-user bearer-token ingest; session-authed reads. +- Server-side validation: per-kind content shape, field length limits, + string-typed source fields, valid-JSON for `json`. + +### Sharing, quotas, security +- Per-artifact public share links (`/s/{token}`), owner-toggled, revocable. +- Per-user disk quotas with oldest-unkept auto-eviction and a Keep flag. +- Allowlist HTML sanitization (nh3) + `script-src 'none'` CSP for the themed + kinds; deep-JSON parse errors return 400 (not 500). + +### UI +- Multi-user auth; feed with source sidebar + label filtering; settings area + (Account, Storage, Demo Data, Users, About); light/dark toggle; app icon. + +### Deployment +- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a + runbook (`deploy/cloud_agent_deploy.md`) and health check. + +[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.1.0...HEAD +[0.1.0]: https://git.cygnus.systems/peter/daybook/releases/tag/v0.1.0 diff --git a/deploy/cloud_agent_deploy.md b/deploy/cloud_agent_deploy.md index d90f5db..daff899 100644 --- a/deploy/cloud_agent_deploy.md +++ b/deploy/cloud_agent_deploy.md @@ -202,25 +202,33 @@ Confirm the response includes `id`, `url`, and `api_url`, and that opening ## 7. Redeploy Loop (routine updates) -This is the command you run every time you're asked to ship a new version: +Releases are tagged `vX.Y.Z` (SemVer). Deploy a specific tag rather than a +moving branch, so what's live is always a known, named version — and note +the currently-deployed one before switching, for a clean rollback target: ```bash cd /opt/daybook -PREV_SHA=$(git rev-parse HEAD) -git pull +PREV_TAG=$(git describe --tags --exact-match 2>/dev/null || git rev-parse HEAD) # what's live now +git fetch --tags +git checkout v0.1.0 # the tag you were told to deploy (latest: git tag -l | sort -V | tail -1) docker compose -f deploy/docker-compose.yml up -d --build ``` -Then repeat the verification in step 6 (capabilities check at minimum). If it -fails: +Then repeat the verification in step 6 (and confirm `/api/capabilities` +reports the `version` you expect). If it fails, roll back to the previous +version and report the failure (build log / migration error / failed health +check) rather than retrying blindly: ```bash -git checkout "$PREV_SHA" +git checkout "$PREV_TAG" docker compose -f deploy/docker-compose.yml up -d --build ``` -to roll back, then report the failure (build log / migration error / failed -health check) back to the operator rather than retrying blindly. +Caveat: rollback reverts code, not data — a release that already applied a +DB migration may not cleanly downgrade against the migrated `db.sqlite3`. +For a routine forward release that's fine; if a rollback is needed after a +migration, restore the pre-deploy `deploy/data` backup (see §9) alongside +the code checkout. `deploy/.env` and the data directory (wherever `DAYBOOK_HOST_DATA_DIR` points, or `deploy/data` by default) are untracked, so `git pull` never touches them — From caeb2bedc7b2ce0cc845933a257ad51164c949ee Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 3 Sep 2026 15:43:58 +0100 Subject: [PATCH 2/4] Rewrite posting skill to be location-agnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skill was authored in this dev workspace, where the Daybook server, the skill, and the posting agent all share one directory — so it read as if the agent always runs from a Daybook source checkout on localhost. - State up front that you post to a running instance that may be anywhere, need no source checkout, and are tied to it only via DAYBOOK_URL/TOKEN. - Drop "from the project root"; reference the bundled helper by absolute path (DAYBOOK_POST=.../this-skill/scripts/daybook_post.py). - Reframe DAYBOOK_URL as set-this-to-your-instance; label 127.0.0.1:8001 as the local dev fallback only. - Genericize the hardcoded localhost URL in the image-embed example. - Add a raw curl fallback for environments without the Python helper. - Keep frontmatter to name+description (the Skills-API-portable subset). Co-Authored-By: Claude Opus 4.8 --- skills/daybook-posting/SKILL.md | 120 ++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 30 deletions(-) diff --git a/skills/daybook-posting/SKILL.md b/skills/daybook-posting/SKILL.md index 51f4dc5..c972af9 100644 --- a/skills/daybook-posting/SKILL.md +++ b/skills/daybook-posting/SKILL.md @@ -1,26 +1,13 @@ --- name: daybook-posting -description: Use when you have produced a finished deliverable such as a report, summary, brief, diagram, generated HTML, PDF, deck, or status update that a human should review later outside this chat. Publishes the artifact to Daybook and returns a URL. +description: Use when you have produced a finished deliverable such as a report, summary, brief, diagram, generated HTML, PDF, deck, or status update that a human should review later outside this chat. Publishes the artifact to a Daybook instance and returns a URL. --- # Daybook Posting Daybook is a private artifact journal for completed agent outputs. It stores, indexes, and displays artifacts for human review. It does not execute commands, run workflows, or continue conversations. -Always use the helper script. Do not hand-build curl, JSON, PowerShell multipart, or inline shell content. - -From the project root: - -```bash -python skills/daybook-posting/scripts/daybook_post.py --check -python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Build Report" --content-file report.md -cat report.md | python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Build Report" -python skills/daybook-posting/scripts/daybook_post.py --file diagram.svg --title "Architecture Diagram" -``` - -The project also keeps a compatibility wrapper at `scripts/daybook_post.py`, but the canonical helper is bundled inside this skill. - -The artifact URL is printed as the final stdout line. +You post to a **running Daybook server**, which may be anywhere — a cloud VM, a container, or a workstation. You do **not** need a checkout of Daybook's source. The only things that tie you to a particular instance are two environment variables: `DAYBOOK_URL` (where the server is) and `DAYBOOK_TOKEN` (who you are). Set those and the rest of this skill works unchanged regardless of where you or the server run. ## Configuration @@ -30,12 +17,54 @@ config) — the helper reads `os.environ` directly. There is no `.env` file support; a `.env` file in the working directory is not read automatically. ```text -DAYBOOK_URL=http://127.0.0.1:8001 -DAYBOOK_TOKEN=your-personal-api-token -DAYBOOK_SOURCE=agent +DAYBOOK_URL=https://your-daybook-instance.example.com # your server's base URL +DAYBOOK_TOKEN=your-personal-api-token # from Settings -> Account +DAYBOOK_SOURCE=agent # a stable label for what's posting ``` -`DAYBOOK_TOKEN` is your personal per-user API token, from Settings → Account in the app (not a token shared by everyone). Artifacts posted with your token land only in your feed. +- **`DAYBOOK_URL`** — the base URL of the Daybook instance you're posting to. + Set it to your server. If it's unset, the helper falls back to + `http://127.0.0.1:8001`, which is only correct when Daybook is running + locally on the same machine (its development default). For any remote or + deployed instance you **must** set this. +- **`DAYBOOK_TOKEN`** — your personal per-user API token, from Settings → + Account in the app (not a token shared by everyone). Artifacts posted with + your token land only in your feed. +- **`DAYBOOK_SOURCE`** — a free-text label for whatever is posting + (agent-profile, script, or person name). Optional; can also be passed per + call with `--source`. + +## The Helper + +Always use the bundled helper script. Do not hand-build curl, JSON, +PowerShell multipart, or inline shell content (a `curl` fallback for +environments without the helper is documented at the end — the script is +still preferred everywhere it's available). + +The helper lives at **`scripts/daybook_post.py` inside this skill's own +directory** — not in any project you happen to be working in. Invoke it by +its absolute path. For brevity, the examples below assume you've captured +that path once: + +```bash +# Point this at the helper inside THIS skill directory (absolute path). +DAYBOOK_POST=/abs/path/to/this/skill/scripts/daybook_post.py +``` + +Then: + +```bash +python "$DAYBOOK_POST" --check +python "$DAYBOOK_POST" --kind markdown --title "Build Report" --content-file report.md +cat report.md | python "$DAYBOOK_POST" --kind markdown --title "Build Report" +python "$DAYBOOK_POST" --file diagram.svg --title "Architecture Diagram" +``` + +The artifact URL is printed as the final stdout line. + +(If you *are* working inside a Daybook source checkout, a compatibility +wrapper also exists at `scripts/daybook_post.py` in the repo root, but the +canonical helper is the one bundled in this skill.) ## Kind Cheat Sheet @@ -65,8 +94,9 @@ presentation. This script only **creates** artifacts — none of the following are possible through it. Updating an existing artifact's title/tags/metadata, -or deleting one, requires the raw API (see `docs/api.md`); enabling its -public share link is feed-UI-only, with no API equivalent at all yet. +or deleting one, requires the raw API (see the instance's `docs/api.md` or +`GET /api/capabilities`); enabling its public share link is feed-UI-only, +with no API equivalent at all yet. ## CDN Snippets For `html_full` @@ -119,11 +149,12 @@ artifact), upload it first and reference the returned URL: ```bash # 1. Upload -> prints the URL as the last line -python skills/daybook-posting/scripts/daybook_post.py --upload-image chart.png +python "$DAYBOOK_POST" --upload-image chart.png -# 2. Reference that URL in your markdown, then post as usual -# ![chart](http://127.0.0.1:8001/assets/ab12cd34ef56) -python skills/daybook-posting/scripts/daybook_post.py report.md +# 2. Reference that URL in your markdown, then post as usual. +# The printed URL is absolute against your DAYBOOK_URL, e.g. +# ![chart]($DAYBOOK_URL/assets/ab12cd34ef56) +python "$DAYBOOK_POST" report.md ``` The image is linked to the artifact that references it and is deleted along with it. @@ -136,19 +167,19 @@ just post the file positionally; auto-mode detects `.csv`/`.json` and picks the right kind automatically: ```bash -python skills/daybook-posting/scripts/daybook_post.py results.csv -python skills/daybook-posting/scripts/daybook_post.py --kind json --title "Run Summary" --content-file summary.json +python "$DAYBOOK_POST" results.csv +python "$DAYBOOK_POST" --kind json --title "Run Summary" --content-file summary.json ``` `.xlsx` spreadsheets work too, with no special flag needed — just `--file` (or post it positionally, same as any other file): ```bash -python skills/daybook-posting/scripts/daybook_post.py balance_sheet.xlsx +python "$DAYBOOK_POST" balance_sheet.xlsx ``` The server converts it to a `csv` artifact automatically. Known -limitations (see `docs/api.md` for the full list): +limitations (see the instance's `docs/api.md` for the full list): - Only the workbook's **active sheet** converts; other sheets are dropped. - Formula cells need to have been calculated and saved by real spreadsheet @@ -165,7 +196,7 @@ as a binary, use `--file path --as-markdown` to wrap it in a Markdown fenced code block (still gets syntax highlighting): ```bash -python skills/daybook-posting/scripts/daybook_post.py --file script.py --as-markdown --title "Generated Script" +python "$DAYBOOK_POST" --file script.py --as-markdown --title "Generated Script" ``` Without `--as-markdown`, it uploads as a downloadable binary file instead. @@ -189,8 +220,37 @@ context like `run_id` or `model` — these end up in the artifact's ## Rules - Run `--check` first if unsure Daybook is reachable. +- Make sure `DAYBOOK_URL` points at the right instance and `DAYBOOK_TOKEN` is set before posting anything real. - Use a stable `DAYBOOK_SOURCE` or pass `--source` (the agent-profile, script, or person name that produced this artifact). - Use `--tags a,b,c` for filtering. - Use `--meta run_id=... --meta model=...` for machine-readable context. - Never inline multiline content into the shell command; use a file or stdin. - Add `--dry-run` to see exactly what would be sent (method, headers, payload) without actually posting — useful when unsure a payload is well-formed. + +## `curl` Fallback (No Helper Available) + +Prefer the helper above — it handles multipart, content-type inference, +JSON shaping, and provenance flags for you. Use raw `curl` **only** when you +genuinely can't run the bundled Python script (e.g. a minimal environment +with no Python). Text artifacts post as JSON to `POST /api/artifacts` with +bearer-token auth: + +```bash +curl -sS -X POST "$DAYBOOK_URL/api/artifacts" \ + -H "Authorization: Bearer $DAYBOOK_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"kind":"markdown","title":"Build Report","source":"agent","content":"# Hello\n\nBody text."}' +``` + +File/image artifacts use multipart instead: + +```bash +curl -sS -X POST "$DAYBOOK_URL/api/artifacts" \ + -H "Authorization: Bearer $DAYBOOK_TOKEN" \ + -F "kind=image" -F "title=Architecture Diagram" -F "source=agent" \ + -F "file=@diagram.png" +``` + +`GET $DAYBOOK_URL/api/capabilities` (no auth needed beyond a login/token per +that instance's config) lists supported kinds, methods, and the server +version if you need to confirm the contract before posting. From fb692612187f09bb32a5b36ea40e6553c5006b0e Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 3 Sep 2026 15:50:04 +0100 Subject: [PATCH 3/4] Add GET /api/skill endpoint serving the posting skill Serves skills/daybook-posting/SKILL.md so users/agents can fetch the posting instructions from the running instance instead of hunting for the file. The server rewrites the DAYBOOK_URL config line to the request's own origin, so the text handed to an agent already points at the right server; the recipient only supplies their token. - Same auth as /api/capabilities (session or bearer token); no user data. - Raw text/markdown by default, ?format=json returns {version, url, skill}. - Advertised via skill_url + a /api/skill methods entry in capabilities. - Ships in the Docker image (COPY . /app, skills/ not dockerignored). - Tests for auth, origin rewrite, JSON mode, session auth, capabilities ad. - docs/api.md + CHANGELOG updated. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 11 ++++++++- artifacts/urls.py | 1 + artifacts/views.py | 46 +++++++++++++++++++++++++++++++++++++ docs/api.md | 34 +++++++++++++++++++++++++-- tests/api/test_artifacts.py | 37 +++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ce888e..4281694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,16 @@ the git tags (`vX.Y.Z`) and `daybook_project.__version__`. ## [Unreleased] -_Nothing yet._ +### Added +- `GET /api/skill` serves the bundled agent-posting skill as Markdown (or + `?format=json`), with `DAYBOOK_URL` pre-filled to the instance's own + origin so the instructions are ready to hand to an AI. Advertised as + `skill_url` in `/api/capabilities`. + +### Changed +- Rewrote the posting skill (`skills/daybook-posting/SKILL.md`) to be + location-agnostic: no longer assumes a local source checkout, reframes + `DAYBOOK_URL` as set-your-instance, and adds a raw `curl` fallback. ## [0.1.0] — 2026-09-03 diff --git a/artifacts/urls.py b/artifacts/urls.py index 98323e3..dae96dc 100644 --- a/artifacts/urls.py +++ b/artifacts/urls.py @@ -14,6 +14,7 @@ urlpatterns = [ path("assets/", views.asset_view, name="asset"), path("api/assets", views.AssetUploadView.as_view(), name="asset-upload-api"), path("api/capabilities", views.CapabilitiesView.as_view(), name="capabilities-api"), + path("api/skill", views.SkillView.as_view(), name="skill-api"), path("api/artifacts", views.ArtifactListCreateView.as_view(), name="artifact-api"), path("api/artifacts/", views.ArtifactDetailView.as_view(), name="artifact-detail-api"), path("settings/", views.settings_home, name="settings-home"), diff --git a/artifacts/views.py b/artifacts/views.py index 555c121..a6ec867 100644 --- a/artifacts/views.py +++ b/artifacts/views.py @@ -322,11 +322,57 @@ class CapabilitiesView(APIView): "/api/artifacts": ["GET", "POST"], "/api/artifacts/{id}": ["GET", "PATCH", "DELETE"], "/api/assets": ["POST"], + "/api/capabilities": ["GET"], + "/api/skill": ["GET"], }, + "skill_url": request.build_absolute_uri(reverse("skill-api")), } ) +SKILL_MD_PATH = settings.BASE_DIR / "skills" / "daybook-posting" / "SKILL.md" + + +def render_posting_skill(origin): + """Return the bundled posting SKILL.md with its DAYBOOK_URL placeholder + rewritten to this instance's origin, so text handed to an agent already + points at the right server. Only the config-block assignment is touched; + the surrounding prose (which explains DAYBOOK_URL) is left alone.""" + text = SKILL_MD_PATH.read_text(encoding="utf-8") + return re.sub( + r"(?m)^(DAYBOOK_URL=)\S+", + lambda m: m.group(1) + origin, + text, + ) + + +class SkillView(APIView): + """Serve the bundled agent-posting skill (skills/daybook-posting/SKILL.md), + with DAYBOOK_URL pre-filled to this instance so a recipient posts to the + right server. Same auth as capabilities: a logged-in user can copy it, or + an agent can fetch it with its API token. + + GET /api/skill -> raw Markdown (text/markdown) + GET /api/skill?format=json -> {"version", "url", "skill"} + """ + + def get(self, request): + if not request.user.is_authenticated and resolve_token_user(request) is None: + return HttpResponseForbidden("Login or a valid API token is required") + + origin = request.build_absolute_uri("/").rstrip("/") + try: + skill = render_posting_skill(origin) + except FileNotFoundError: + raise Http404("Posting skill is not bundled with this deployment") + + if request.GET.get("format") == "json": + from daybook_project import __version__ + + return Response({"version": __version__, "url": origin, "skill": skill}) + return HttpResponse(skill, content_type="text/markdown; charset=utf-8") + + FEED_WINDOW_DAYS = 7 diff --git a/docs/api.md b/docs/api.md index 8d95c6f..b3778c2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -529,8 +529,11 @@ Example response: "methods": { "/api/artifacts": ["GET", "POST"], "/api/artifacts/{id}": ["GET", "PATCH", "DELETE"], - "/api/assets": ["POST"] - } + "/api/assets": ["POST"], + "/api/capabilities": ["GET"], + "/api/skill": ["GET"] + }, + "skill_url": "http://127.0.0.1:8001/api/skill" } ``` @@ -545,6 +548,33 @@ endpoints, it accepts a bearer token as well as a browser session: curl -H "Authorization: Bearer " http://127.0.0.1:8001/api/capabilities ``` +## Posting Skill + +Endpoint: + +```http +GET /api/skill +GET /api/skill?format=json +``` + +Returns the bundled agent-posting skill (`skills/daybook-posting/SKILL.md`) — +the same instructions you'd give an AI so it knows how to publish to this +instance. The server rewrites the `DAYBOOK_URL` line to **this instance's own +origin**, so the text a caller receives already points at the right server; +a recipient only has to fill in their `DAYBOOK_TOKEN`. + +Same auth as capabilities (a browser session **or** a bearer token), so a +logged-in user can copy it to hand to their agent, or an agent can fetch it +directly. The default response is raw Markdown (`text/markdown`); add +`?format=json` for `{"version", "url", "skill"}`. + +```bash +# Fetch the ready-to-use instructions for this instance +curl -H "Authorization: Bearer " http://127.0.0.1:8001/api/skill +``` + +Discover it programmatically via `skill_url` in the capabilities response. + ## Current MVP Constraints - Each user has a personal ingest API token; there is no shared token. diff --git a/tests/api/test_artifacts.py b/tests/api/test_artifacts.py index 967ed4f..f801988 100644 --- a/tests/api/test_artifacts.py +++ b/tests/api/test_artifacts.py @@ -12,6 +12,7 @@ from django.utils import timezone from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase +from daybook_project import __version__ from artifacts.models import Artifact User = get_user_model() @@ -78,6 +79,42 @@ class ArtifactAPITestCase(APITestCase): response = self.client.get(reverse("capabilities-api")) self.assertEqual(response.status_code, 200) + def test_capabilities_advertises_skill(self): + response = self.client.get(reverse("capabilities-api"), **self.auth_headers(self.token)) + self.assertEqual(response.data["methods"]["/api/skill"], ["GET"]) + self.assertTrue(response.data["skill_url"].endswith("/api/skill")) + + # -- skill ---------------------------------------------------------- + + def test_skill_requires_auth(self): + response = self.client.get(reverse("skill-api")) + self.assertEqual(response.status_code, 403) + + def test_skill_returns_markdown_with_origin_filled_in(self): + response = self.client.get(reverse("skill-api"), **self.auth_headers(self.token)) + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "text/markdown; charset=utf-8") + body = response.content.decode() + # Frontmatter is intact and the config placeholder is rewritten to this + # instance's origin (http://testserver under the test client). + self.assertIn("name: daybook-posting", body) + self.assertIn("DAYBOOK_URL=http://testserver", body) + self.assertNotIn("your-daybook-instance.example.com", body) + + def test_skill_json_format(self): + response = self.client.get( + reverse("skill-api"), {"format": "json"}, **self.auth_headers(self.token) + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["version"], __version__) + self.assertEqual(response.data["url"], "http://testserver") + self.assertIn("name: daybook-posting", response.data["skill"]) + + def test_skill_with_session(self): + self.client.force_login(self.user) + response = self.client.get(reverse("skill-api")) + self.assertEqual(response.status_code, 200) + # -- create (POST) -------------------------------------------------- def test_create_markdown_artifact(self): From 5c46f2aa67a53b31f9da39ac32696536f2da0444 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 3 Sep 2026 16:13:19 +0100 Subject: [PATCH 4/4] Release v0.1.1: fix collectstatic crash-loop; ship /api/skill Fixes the deploy blocker found deploying v0.1.0 to cloud-vm-1: the vendored gridjs.umd.js ended with a //# sourceMappingURL=gridjs.umd.js.map trailer, but that .map isn't in the repo. The strict CompressedManifestStaticFilesStorage backend follows the reference and raises MissingFileError, aborting the entrypoint's collectstatic so the container never served. Stripped the debug-only trailer (no runtime effect); collectstatic now succeeds from a pristine checkout (169 copied, 481 post-processed). Also rolls up this release's already-committed work: GET /api/skill and the location-agnostic posting-skill rewrite. - Bump __version__ to 0.1.1. - Runbook: note that a post-build crash-loop is most likely a collectstatic MissingFileError (check docker logs), and how to clear on-server tracked drift before checking out a tag. - CHANGELOG: 0.1.1 entry. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 13 ++++++++++++- daybook_project/__init__.py | 2 +- deploy/cloud_agent_deploy.md | 14 ++++++++++++++ static/vendor/gridjs/gridjs.umd.js | 1 - 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4281694..6520e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ the git tags (`vX.Y.Z`) and `daybook_project.__version__`. ## [Unreleased] +_Nothing yet._ + +## [0.1.1] — 2026-09-03 + +### Fixed +- Deployment crash-loop: the vendored `gridjs.umd.js` referenced a + `gridjs.umd.js.map` sourcemap that isn't in the repo, so `collectstatic` + (strict manifest storage) failed on container start and v0.1.0 never came + up. Removed the dangling sourcemap trailer (debug-only, no runtime effect). + ### Added - `GET /api/skill` serves the bundled agent-posting skill as Markdown (or `?format=json`), with `DAYBOOK_URL` pre-filled to the instance's own @@ -53,5 +63,6 @@ First tagged release. - Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a runbook (`deploy/cloud_agent_deploy.md`) and health check. -[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.1.0...HEAD +[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.1.1...HEAD +[0.1.1]: https://git.cygnus.systems/peter/daybook/compare/v0.1.0...v0.1.1 [0.1.0]: https://git.cygnus.systems/peter/daybook/releases/tag/v0.1.0 diff --git a/daybook_project/__init__.py b/daybook_project/__init__.py index 29f4719..e27f5fc 100644 --- a/daybook_project/__init__.py +++ b/daybook_project/__init__.py @@ -1,4 +1,4 @@ # Single source of truth for the app version. Bump this, commit, then tag the # commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in # GET /api/capabilities ("version"). -__version__ = "0.1.0" +__version__ = "0.1.1" diff --git a/deploy/cloud_agent_deploy.md b/deploy/cloud_agent_deploy.md index daff899..c369211 100644 --- a/deploy/cloud_agent_deploy.md +++ b/deploy/cloud_agent_deploy.md @@ -234,6 +234,20 @@ the code checkout. or `deploy/data` by default) are untracked, so `git pull` never touches them — no need to back them up before a routine redeploy. +**If the container crash-loops right after a build** (`docker compose ps` +shows `restarting`, health never goes green), the most likely cause is the +entrypoint's `collectstatic` step failing — check `docker logs daybook` +before assuming an env/network problem. A `whitenoise ... MissingFileError` +there means a static asset references a file that isn't present (this is what +bit v0.1.0). No data or schema impact; fix the asset in the repo, tag, and +redeploy. + +**If `git checkout ` refuses because of local working-tree changes** to +tracked files (e.g. a previous on-server hotfix), reset those tracked files +first — `git checkout -- ` for a specific one, or `git stash` / +`git reset --hard` to clear all tracked drift. This never touches `deploy/.env` +or the data directory, which are untracked and therefore safe. + ## 8. Hardening For Internet-Facing Deployments If this VM is reachable from the public internet (not just a private diff --git a/static/vendor/gridjs/gridjs.umd.js b/static/vendor/gridjs/gridjs.umd.js index 31761d7..6e79315 100644 --- a/static/vendor/gridjs/gridjs.umd.js +++ b/static/vendor/gridjs/gridjs.umd.js @@ -1,2 +1 @@ !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t||self).gridjs={})}(this,function(t){function n(t,n){for(var e=0;et.length)&&(n=t.length);for(var e=0,r=new Array(n);e=t.length?{done:!0}:{done:!1,value:t[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var l;!function(t){t[t.Init=0]="Init",t[t.Loading=1]="Loading",t[t.Loaded=2]="Loaded",t[t.Rendered=3]="Rendered",t[t.Error=4]="Error"}(l||(l={}));var c,f,p,d,h,_,m,v={},g=[],y=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function b(t,n){for(var e in n)t[e]=n[e];return t}function w(t){var n=t.parentNode;n&&n.removeChild(t)}function x(t,n,e){var r,o,i,u={};for(i in n)"key"==i?r=n[i]:"ref"==i?o=n[i]:u[i]=n[i];if(arguments.length>2&&(u.children=arguments.length>3?c.call(arguments,2):e),"function"==typeof t&&null!=t.defaultProps)for(i in t.defaultProps)void 0===u[i]&&(u[i]=t.defaultProps[i]);return k(t,u,r,o,null)}function k(t,n,e,r,o){var i={type:t,props:n,key:e,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++p:o};return null==o&&null!=f.vnode&&f.vnode(i),i}function P(t){return t.children}function S(t,n){this.props=t,this.context=n}function N(t,n){if(null==n)return t.__?N(t.__,t.__.__k.indexOf(t)+1):null;for(var e;n0?k(d.type,d.props,d.key,d.ref?d.ref:null,d.__v):d)){if(d.__=e,d.__b=e.__b+1,null===(p=y[c])||p&&d.key==p.key&&d.type===p.type)y[c]=void 0;else for(f=0;f0&&(this.callbacks[e].forEach(function(t){return t.apply(void 0,[].slice.call(n,1))}),!0)},t}();function Y(t,n){if(typeof t!=typeof n)return!1;if(null===t&&null===n)return!0;if("object"!=typeof t)return t===n;if(Array.isArray(t)&&Array.isArray(n)){if(t.length!==n.length)return!1;for(var e=0;e=e.__.length&&e.__.push({__V:ft}),e.__[t]}function gt(t){return lt=1,function(t,n,e){var r=vt(ot++,2);if(r.t=t,!r.__c&&(r.__=[Et(void 0,n),function(t){var n=r.__N?r.__N[0]:r.__[0],e=r.t(n,t);n!==e&&(r.__N=[e,r.__[1]],r.__c.setState({}))}],r.__c=it,!it.u)){it.u=!0;var o=it.shouldComponentUpdate;it.shouldComponentUpdate=function(t,n,e){if(!r.__c.__H)return!0;var i=r.__c.__H.__.filter(function(t){return t.__c});if(i.every(function(t){return!t.__N}))return!o||o.call(this,t,n,e);var u=!1;return i.forEach(function(t){if(t.__N){var n=t.__[0];t.__=t.__N,t.__N=void 0,n!==t.__[0]&&(u=!0)}}),!(!u&&r.__c.props===t)&&(!o||o.call(this,t,n,e))}}return r.__N||r.__}(Et,t)}function yt(t,n){var e=vt(ot++,3);!f.__s&&Ct(e.__H,n)&&(e.__=t,e.i=n,it.__H.__h.push(e))}function bt(t){return lt=5,wt(function(){return{current:t}},[])}function wt(t,n){var e=vt(ot++,7);return Ct(e.__H,n)?(e.__V=t(),e.i=n,e.__h=t,e.__V):e.__}function xt(){for(var t;t=ct.shift();)if(t.__P&&t.__H)try{t.__H.__h.forEach(St),t.__H.__h.forEach(Nt),t.__H.__h=[]}catch(n){t.__H.__h=[],f.__e(n,t.__v)}}f.__b=function(t){it=null,pt&&pt(t)},f.__r=function(t){dt&&dt(t),ot=0;var n=(it=t.__c).__H;n&&(ut===it?(n.__h=[],it.__h=[],n.__.forEach(function(t){t.__N&&(t.__=t.__N),t.__V=ft,t.__N=t.i=void 0})):(n.__h.forEach(St),n.__h.forEach(Nt),n.__h=[])),ut=it},f.diffed=function(t){ht&&ht(t);var n=t.__c;n&&n.__H&&(n.__H.__h.length&&(1!==ct.push(n)&&st===f.requestAnimationFrame||((st=f.requestAnimationFrame)||Pt)(xt)),n.__H.__.forEach(function(t){t.i&&(t.__H=t.i),t.__V!==ft&&(t.__=t.__V),t.i=void 0,t.__V=ft})),ut=it=null},f.__c=function(t,n){n.some(function(t){try{t.__h.forEach(St),t.__h=t.__h.filter(function(t){return!t.__||Nt(t)})}catch(e){n.some(function(t){t.__h&&(t.__h=[])}),n=[],f.__e(e,t.__v)}}),_t&&_t(t,n)},f.unmount=function(t){mt&&mt(t);var n,e=t.__c;e&&e.__H&&(e.__H.__.forEach(function(t){try{St(t)}catch(t){n=t}}),e.__H=void 0,n&&f.__e(n,e.__v))};var kt="function"==typeof requestAnimationFrame;function Pt(t){var n,e=function(){clearTimeout(r),kt&&cancelAnimationFrame(n),setTimeout(t)},r=setTimeout(e,100);kt&&(n=requestAnimationFrame(e))}function St(t){var n=it,e=t.__c;"function"==typeof e&&(t.__c=void 0,e()),it=n}function Nt(t){var n=it;t.__c=t.__(),it=n}function Ct(t,n){return!t||t.length!==n.length||n.some(function(n,e){return n!==t[e]})}function Et(t,n){return"function"==typeof n?n(t):n}function It(){return function(t){var n=it.context[t.__c],e=vt(ot++,9);return e.c=t,n?(null==e.__&&(e.__=!0,n.sub(it)),n.props.value):t.__}(fn)}var Tt={search:{placeholder:"Type a keyword..."},sort:{sortAsc:"Sort column ascending",sortDesc:"Sort column descending"},pagination:{previous:"Previous",next:"Next",navigate:function(t,n){return"Page "+t+" of "+n},page:function(t){return"Page "+t},showing:"Showing",of:"of",to:"to",results:"results"},loading:"Loading...",noRecordsFound:"No matching records found",error:"An error happened while fetching the data"},Lt=/*#__PURE__*/function(){function t(t){this._language=void 0,this._defaultLanguage=void 0,this._language=t,this._defaultLanguage=Tt}var n=t.prototype;return n.getString=function(t,n){if(!n||!t)return null;var e=t.split("."),r=e[0];if(n[r]){var o=n[r];return"string"==typeof o?function(){return o}:"function"==typeof o?o:this.getString(e.slice(1).join("."),o)}return null},n.translate=function(t){var n,e=this.getString(t,this._language);return(n=e||this.getString(t,this._defaultLanguage))?n.apply(void 0,[].slice.call(arguments,1)):t},t}();function At(){var t=It();return function(n){var e;return(e=t.translator).translate.apply(e,[n].concat([].slice.call(arguments,1)))}}var Ot=function(t){return function(n){return r({},n,{search:{keyword:t}})}};function jt(){return It().store}function Ht(t){var n=jt(),e=gt(t(n.getState())),r=e[0],o=e[1];return yt(function(){return n.subscribe(function(){var e=t(n.getState());r!==e&&o(e)})},[]),r}function Dt(){var t,n=gt(void 0),e=n[0],r=n[1],o=It(),i=o.search,u=At(),s=jt().dispatch,a=Ht(function(t){return t.search});yt(function(){e&&e.setProps({keyword:null==a?void 0:a.keyword})},[a,e]),yt(function(){r(i.server?new at({keyword:i.keyword,url:i.server.url,body:i.server.body}):new nt({keyword:i.keyword,columns:o.header&&o.header.columns,ignoreHiddenColumns:i.ignoreHiddenColumns||void 0===i.ignoreHiddenColumns,selector:i.selector})),i.keyword&&s(Ot(i.keyword))},[i]),yt(function(){if(e)return o.pipeline.register(e),function(){return o.pipeline.unregister(e)}},[o,e]);var l,c,f,p=function(t,n){return lt=8,wt(function(){return t},n)}((l=function(t){t.target instanceof HTMLInputElement&&s(Ot(t.target.value))},c=e instanceof at?i.debounceTimeout||250:0,function(){var t=arguments;return new Promise(function(n){f&&clearTimeout(f),f=setTimeout(function(){return n(l.apply(void 0,[].slice.call(t)))},c)})}),[i,e]);return x("div",{className:et(rt("search",null==(t=o.className)?void 0:t.search))},x("input",{type:"search",placeholder:u("search.placeholder"),"aria-label":u("search.placeholder"),onInput:p,className:rt(et("input"),et("search","input")),defaultValue:(null==a?void 0:a.keyword)||""}))}var Mt=/*#__PURE__*/function(t){function n(){return t.apply(this,arguments)||this}o(n,t);var r=n.prototype;return r.validateProps=function(){if(isNaN(Number(this.props.limit))||isNaN(Number(this.props.page)))throw Error("Invalid parameters passed")},r._process=function(t){var n=this.props.page;return new J(t.rows.slice(n*this.props.limit,(n+1)*this.props.limit))},e(n,[{key:"type",get:function(){return K.Limit}}]),n}(tt),Rt=/*#__PURE__*/function(t){function n(){return t.apply(this,arguments)||this}return o(n,t),n.prototype._process=function(t){var n={};return this.props.url&&(n.url=this.props.url(t.url,this.props.page,this.props.limit)),this.props.body&&(n.body=this.props.body(t.body,this.props.page,this.props.limit)),r({},t,n)},e(n,[{key:"type",get:function(){return K.ServerLimit}}]),n}(tt);function Ft(){var t=It(),n=t.pagination,e=n.server,r=n.summary,o=void 0===r||r,i=n.nextButton,u=void 0===i||i,s=n.prevButton,a=void 0===s||s,l=n.buttonsCount,c=void 0===l?3:l,f=n.limit,p=void 0===f?10:f,d=n.page,h=void 0===d?0:d,_=n.resetPageOnUpdate,m=void 0===_||_,v=bt(null),g=gt(h),y=g[0],b=g[1],w=gt(0),k=w[0],S=w[1],N=At();yt(function(){return e?(v.current=new Rt({limit:p,page:y,url:e.url,body:e.body}),t.pipeline.register(v.current)):(v.current=new Mt({limit:p,page:y}),t.pipeline.register(v.current)),v.current instanceof Rt?t.pipeline.on("afterProcess",function(t){return S(t.length)}):v.current instanceof Mt&&v.current.on("beforeProcess",function(t){return S(t.length)}),t.pipeline.on("updated",C),t.pipeline.on("error",function(){S(0),b(0)}),function(){t.pipeline.unregister(v.current),t.pipeline.off("updated",C)}},[]);var C=function(t){m&&t!==v.current&&(b(0),0!==v.current.props.page&&v.current.setProps({page:0}))},E=function(){return Math.ceil(k/p)},I=function(t){if(t>=E()||t<0||t===y)return null;b(t),v.current.setProps({page:t})};return x("div",{className:rt(et("pagination"),t.className.pagination)},x(P,null,o&&k>0&&x("div",{role:"status","aria-live":"polite",className:rt(et("summary"),t.className.paginationSummary),title:N("pagination.navigate",y+1,E())},N("pagination.showing")," ",x("b",null,N(""+(y*p+1)))," ",N("pagination.to")," ",x("b",null,N(""+Math.min((y+1)*p,k)))," ",N("pagination.of")," ",x("b",null,N(""+k))," ",N("pagination.results"))),x("div",{className:et("pages")},a&&x("button",{tabIndex:0,role:"button",disabled:0===y,onClick:function(){return I(y-1)},title:N("pagination.previous"),"aria-label":N("pagination.previous"),className:rt(t.className.paginationButton,t.className.paginationButtonPrev)},N("pagination.previous")),function(){if(c<=0)return null;var n=Math.min(E(),c),e=Math.min(y,Math.floor(n/2));return y+Math.floor(n/2)>=E()&&(e=n-(E()-y)),x(P,null,E()>n&&y-e>0&&x(P,null,x("button",{tabIndex:0,role:"button",onClick:function(){return I(0)},title:N("pagination.firstPage"),"aria-label":N("pagination.firstPage"),className:t.className.paginationButton},N("1")),x("button",{tabIndex:-1,className:rt(et("spread"),t.className.paginationButton)},"...")),Array.from(Array(n).keys()).map(function(t){return y+(t-e)}).map(function(n){return x("button",{tabIndex:0,role:"button",onClick:function(){return I(n)},className:rt(y===n?rt(et("currentPage"),t.className.paginationButtonCurrent):null,t.className.paginationButton),title:N("pagination.page",n+1),"aria-label":N("pagination.page",n+1)},N(""+(n+1)))}),E()>n&&E()>y+e+1&&x(P,null,x("button",{tabIndex:-1,className:rt(et("spread"),t.className.paginationButton)},"..."),x("button",{tabIndex:0,role:"button",onClick:function(){return I(E()-1)},title:N("pagination.page",E()),"aria-label":N("pagination.page",E()),className:t.className.paginationButton},N(""+E()))))}(),u&&x("button",{tabIndex:0,role:"button",disabled:E()===y+1||0===E(),onClick:function(){return I(y+1)},title:N("pagination.next"),"aria-label":N("pagination.next"),className:rt(t.className.paginationButton,t.className.paginationButtonNext)},N("pagination.next"))))}function Ut(t,n){return"string"==typeof t?t.indexOf("%")>-1?n/100*parseInt(t,10):parseInt(t,10):t}function Wt(t){return t?Math.floor(t)+"px":""}function Bt(t){var n=t.tableRef.cloneNode(!0);return n.style.position="absolute",n.style.width="100%",n.style.zIndex="-2147483640",n.style.visibility="hidden",x("div",{ref:function(t){t&&t.appendChild(n)}})}function qt(t){if(!t)return"";var n=t.split(" ");return 1===n.length&&/([a-z][A-Z])+/g.test(t)?t:n.map(function(t,n){return 0==n?t.toLowerCase():t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()}).join("")}var zt,Vt=new(/*#__PURE__*/function(){function t(){}var n=t.prototype;return n.format=function(t,n){return"[Grid.js] ["+n.toUpperCase()+"]: "+t},n.error=function(t,n){void 0===n&&(n=!1);var e=this.format(t,"error");if(n)throw Error(e);console.error(e)},n.warn=function(t){console.warn(this.format(t,"warn"))},n.info=function(t){console.info(this.format(t,"info"))},t}());t.PluginPosition=void 0,(zt=t.PluginPosition||(t.PluginPosition={}))[zt.Header=0]="Header",zt[zt.Footer=1]="Footer",zt[zt.Cell=2]="Cell";var $t=/*#__PURE__*/function(){function t(){this.plugins=void 0,this.plugins=[]}var n=t.prototype;return n.get=function(t){return this.plugins.find(function(n){return n.id===t})},n.add=function(t){return t.id?this.get(t.id)?(Vt.error("Duplicate plugin ID: "+t.id),this):(this.plugins.push(t),this):(Vt.error("Plugin ID cannot be empty"),this)},n.remove=function(t){var n=this.get(t);return n&&this.plugins.splice(this.plugins.indexOf(n),1),this},n.list=function(t){var n;return n=null!=t||null!=t?this.plugins.filter(function(n){return n.position===t}):this.plugins,n.sort(function(t,n){return t.order&&n.order?t.order-n.order:1})},t}();function Gt(t){var n=this,e=It();if(t.pluginId){var o=e.plugin.get(t.pluginId);return o?x(P,{},x(o.component,r({plugin:o},t.props))):null}return void 0!==t.position?x(P,{},e.plugin.list(t.position).map(function(t){return x(t.component,r({plugin:t},n.props.props))})):null}var Kt=/*#__PURE__*/function(n){function i(){var t;return(t=n.call(this)||this)._columns=void 0,t._columns=[],t}o(i,n);var u=i.prototype;return u.adjustWidth=function(t,n,e){var o=t.container,u=t.autoWidth;if(!o)return this;var s=o.clientWidth,l={};n.current&&u&&(q(x(Bt,{tableRef:n.current}),e.current),l=function(t){var n=t.querySelector("table");if(!n)return{};var e=n.className,o=n.style.cssText;n.className=e+" "+et("shadowTable"),n.style.tableLayout="auto",n.style.width="auto",n.style.padding="0",n.style.margin="0",n.style.border="none",n.style.outline="none";var i=Array.from(n.parentNode.querySelectorAll("thead th")).reduce(function(t,n){var e;return n.style.width=n.clientWidth+"px",r(((e={})[n.getAttribute("data-column-id")]={minWidth:n.clientWidth},e),t)},{});return n.className=e,n.style.cssText=o,n.style.tableLayout="auto",Array.from(n.parentNode.querySelectorAll("thead th")).reduce(function(t,n){return t[n.getAttribute("data-column-id")].width=n.clientWidth,t},i)}(e.current));for(var c,f=a(i.tabularFormat(this.columns).reduce(function(t,n){return t.concat(n)},[]));!(c=f()).done;){var p=c.value;p.columns&&p.columns.length>0||(!p.width&&u?p.id in l&&(p.width=Wt(l[p.id].width),p.minWidth=Wt(l[p.id].minWidth)):p.width=Wt(Ut(p.width,s)))}return n.current&&u&&q(null,e.current),this},u.setSort=function(t,n){for(var e,o=a(n||this.columns||[]);!(e=o()).done;){var i=e.value;i.columns&&i.columns.length>0?i.sort=void 0:void 0===i.sort&&t?i.sort={}:i.sort?"object"==typeof i.sort&&(i.sort=r({},i.sort)):i.sort=void 0,i.columns&&this.setSort(t,i.columns)}},u.setResizable=function(t,n){for(var e,r=a(n||this.columns||[]);!(e=r()).done;){var o=e.value;void 0===o.resizable&&(o.resizable=t),o.columns&&this.setResizable(t,o.columns)}},u.setID=function(t){for(var n,e=a(t||this.columns||[]);!(n=e()).done;){var r=n.value;r.id||"string"!=typeof r.name||(r.id=qt(r.name)),r.id||Vt.error('Could not find a valid ID for one of the columns. Make sure a valid "id" is set for all columns.'),r.columns&&this.setID(r.columns)}},u.populatePlugins=function(n,e){for(var o,i=a(e);!(o=i()).done;){var u=o.value;void 0!==u.plugin&&n.add(r({id:u.id},u.plugin,{position:t.PluginPosition.Cell}))}},i.fromColumns=function(t){for(var n,e=new i,r=a(t);!(n=r()).done;){var o=n.value;if("string"==typeof o||d(o))e.columns.push({name:o});else if("object"==typeof o){var u=o;u.columns&&(u.columns=i.fromColumns(u.columns).columns),"object"==typeof u.plugin&&void 0===u.data&&(u.data=null),e.columns.push(o)}}return e},i.createFromConfig=function(t){var n=new i;return t.from?n.columns=i.fromHTMLTable(t.from).columns:t.columns?n.columns=i.fromColumns(t.columns).columns:!t.data||"object"!=typeof t.data[0]||t.data[0]instanceof Array||(n.columns=Object.keys(t.data[0]).map(function(t){return{name:t}})),n.columns.length?(n.setID(),n.setSort(t.sort),n.setResizable(t.resizable),n.populatePlugins(t.plugin,n.columns),n):null},i.fromHTMLTable=function(t){for(var n,e=new i,r=a(t.querySelector("thead").querySelectorAll("th"));!(n=r()).done;){var o=n.value;e.columns.push({name:o.innerHTML,width:o.width})}return e},i.tabularFormat=function(t){var n=[],e=t||[],r=[];if(e&&e.length){n.push(e);for(var o,i=a(e);!(o=i()).done;){var u=o.value;u.columns&&u.columns.length&&(r=r.concat(u.columns))}r.length&&(n=n.concat(this.tabularFormat(r)))}return n},i.leafColumns=function(t){var n=[],e=t||[];if(e&&e.length)for(var r,o=a(e);!(r=o()).done;){var i=r.value;i.columns&&0!==i.columns.length||n.push(i),i.columns&&(n=n.concat(this.leafColumns(i.columns)))}return n},i.maximumDepth=function(t){return this.tabularFormat([t]).length-1},e(i,[{key:"columns",get:function(){return this._columns},set:function(t){this._columns=t}},{key:"visibleColumns",get:function(){return this._columns.filter(function(t){return!t.hidden})}}]),i}(V),Xt=function(){},Zt=/*#__PURE__*/function(t){function n(n){var e;return(e=t.call(this)||this).data=void 0,e.set(n),e}o(n,t);var e=n.prototype;return e.get=function(){try{return Promise.resolve(this.data()).then(function(t){return{data:t,total:t.length}})}catch(t){return Promise.reject(t)}},e.set=function(t){return t instanceof Array?this.data=function(){return t}:t instanceof Function&&(this.data=t),this},n}(Xt),Jt=/*#__PURE__*/function(t){function n(n){var e;return(e=t.call(this)||this).options=void 0,e.options=n,e}o(n,t);var e=n.prototype;return e.handler=function(t){return"function"==typeof this.options.handle?this.options.handle(t):t.ok?t.json():(Vt.error("Could not fetch data: "+t.status+" - "+t.statusText,!0),null)},e.get=function(t){var n=r({},this.options,t);return"function"==typeof n.data?n.data(n):fetch(n.url,n).then(this.handler.bind(this)).then(function(t){return{data:n.then(t),total:"function"==typeof n.total?n.total(t):void 0}})},n}(Xt),Qt=/*#__PURE__*/function(){function t(){}return t.createFromConfig=function(t){var n=null;return t.data&&(n=new Zt(t.data)),t.from&&(n=new Zt(this.tableElementToArray(t.from)),t.from.style.display="none"),t.server&&(n=new Jt(t.server)),n||Vt.error("Could not determine the storage type",!0),n},t.tableElementToArray=function(t){for(var n,e,r=[],o=a(t.querySelector("tbody").querySelectorAll("tr"));!(n=o()).done;){for(var i,u=[],s=a(n.value.querySelectorAll("td"));!(i=s()).done;){var l=i.value;1===l.childNodes.length&&l.childNodes[0].nodeType===Node.TEXT_NODE?u.push((e=l.innerHTML,(new DOMParser).parseFromString(e,"text/html").documentElement.textContent)):u.push(G(l.innerHTML))}r.push(u)}return r},t}(),Yt="undefined"!=typeof Symbol?Symbol.iterator||(Symbol.iterator=Symbol("Symbol.iterator")):"@@iterator";function tn(t,n,e){if(!t.s){if(e instanceof nn){if(!e.s)return void(e.o=tn.bind(null,t,n));1&n&&(n=e.s),e=e.v}if(e&&e.then)return void e.then(tn.bind(null,t,n),tn.bind(null,t,2));t.s=n,t.v=e;var r=t.o;r&&r(t)}}var nn=/*#__PURE__*/function(){function t(){}return t.prototype.then=function(n,e){var r=new t,o=this.s;if(o){var i=1&o?n:e;if(i){try{tn(r,1,i(this.v))}catch(t){tn(r,2,t)}return r}return this}return this.o=function(t){try{var o=t.v;1&t.s?tn(r,1,n?n(o):o):e?tn(r,1,e(o)):tn(r,2,o)}catch(t){tn(r,2,t)}},r},t}();function en(t){return t instanceof nn&&1&t.s}var rn=/*#__PURE__*/function(t){function n(n){var e;return(e=t.call(this)||this)._steps=new Map,e.cache=new Map,e.lastProcessorIndexUpdated=-1,n&&n.forEach(function(t){return e.register(t)}),e}o(n,t);var r=n.prototype;return r.clearCache=function(){this.cache=new Map,this.lastProcessorIndexUpdated=-1},r.register=function(t,n){if(void 0===n&&(n=null),!t)throw Error("Processor is not defined");if(null===t.type)throw Error("Processor type is not defined");if(this.findProcessorIndexByID(t.id)>-1)throw Error("Processor ID "+t.id+" is already defined");return t.on("propsUpdated",this.processorPropsUpdated.bind(this)),this.addProcessorByPriority(t,n),this.afterRegistered(t),t},r.tryRegister=function(t,n){void 0===n&&(n=null);try{return this.register(t,n)}catch(t){}},r.unregister=function(t){if(t&&-1!==this.findProcessorIndexByID(t.id)){var n=this._steps.get(t.type);n&&n.length&&(this._steps.set(t.type,n.filter(function(n){return n!=t})),this.emit("updated",t))}},r.addProcessorByPriority=function(t,n){var e=this._steps.get(t.type);if(!e){var r=[];this._steps.set(t.type,r),e=r}if(null===n||n<0)e.push(t);else if(e[n]){var o=e.slice(0,n-1),i=e.slice(n+1);this._steps.set(t.type,o.concat(t).concat(i))}else e[n]=t},r.getStepsByType=function(t){return this.steps.filter(function(n){return n.type===t})},r.getSortedProcessorTypes=function(){return Object.keys(K).filter(function(t){return!isNaN(Number(t))}).map(function(t){return Number(t)})},r.process=function(t){try{var n=function(t){return e.lastProcessorIndexUpdated=o.length,e.emit("afterProcess",i),i},e=this,r=e.lastProcessorIndexUpdated,o=e.steps,i=t,u=function(t,n){try{var u=function(t,n,e){if("function"==typeof t[Yt]){var r,o,i,u=t[Yt]();if(function t(e){try{for(;!(r=u.next()).done;)if((e=n(r.value))&&e.then){if(!en(e))return void e.then(t,i||(i=tn.bind(null,o=new nn,2)));e=e.v}o?tn(o,1,e):o=e}catch(t){tn(o||(o=new nn),2,t)}}(),u.return){var s=function(t){try{r.done||u.return()}catch(t){}return t};if(o&&o.then)return o.then(s,function(t){throw s(t)});s()}return o}if(!("length"in t))throw new TypeError("Object is not iterable");for(var a=[],l=0;l=r)return Promise.resolve(t.process(i)).then(function(n){e.cache.set(t.id,i=n)});i=e.cache.get(t.id)}();if(o&&o.then)return o.then(function(){})})}catch(t){return n(t)}return u&&u.then?u.then(void 0,n):u}(0,function(t){throw Vt.error(t),e.emit("error",i),t});return Promise.resolve(u&&u.then?u.then(n):n())}catch(t){return Promise.reject(t)}},r.findProcessorIndexByID=function(t){return this.steps.findIndex(function(n){return n.id==t})},r.setLastProcessorIndex=function(t){var n=this.findProcessorIndexByID(t.id);this.lastProcessorIndexUpdated>n&&(this.lastProcessorIndexUpdated=n)},r.processorPropsUpdated=function(t){this.setLastProcessorIndex(t),this.emit("propsUpdated"),this.emit("updated",t)},r.afterRegistered=function(t){this.setLastProcessorIndex(t),this.emit("afterRegister"),this.emit("updated",t)},e(n,[{key:"steps",get:function(){for(var t,n=[],e=a(this.getSortedProcessorTypes());!(t=e()).done;){var r=this._steps.get(t.value);r&&r.length&&(n=n.concat(r))}return n.filter(function(t){return t})}}]),n}(Q),on=/*#__PURE__*/function(t){function n(){return t.apply(this,arguments)||this}return o(n,t),n.prototype._process=function(t){try{return Promise.resolve(this.props.storage.get(t))}catch(t){return Promise.reject(t)}},e(n,[{key:"type",get:function(){return K.Extractor}}]),n}(tt),un=/*#__PURE__*/function(t){function n(){return t.apply(this,arguments)||this}return o(n,t),n.prototype._process=function(t){var n=J.fromArray(t.data);return n.length=t.total,n},e(n,[{key:"type",get:function(){return K.Transformer}}]),n}(tt),sn=/*#__PURE__*/function(t){function n(){return t.apply(this,arguments)||this}return o(n,t),n.prototype._process=function(){return Object.entries(this.props.serverStorageOptions).filter(function(t){return"function"!=typeof t[1]}).reduce(function(t,n){var e;return r({},t,((e={})[n[0]]=n[1],e))},{})},e(n,[{key:"type",get:function(){return K.Initiator}}]),n}(tt),an=/*#__PURE__*/function(t){function n(){return t.apply(this,arguments)||this}o(n,t);var r=n.prototype;return r.castData=function(t){if(!t||!t.length)return[];if(!this.props.header||!this.props.header.columns)return t;var n=Kt.leafColumns(this.props.header.columns);return t[0]instanceof Array?t.map(function(t){var e=0;return n.map(function(n,r){return void 0!==n.data?(e++,"function"==typeof n.data?n.data(t):n.data):t[r-e]})}):"object"!=typeof t[0]||t[0]instanceof Array?[]:t.map(function(t){return n.map(function(n,e){return void 0!==n.data?"function"==typeof n.data?n.data(t):n.data:n.id?t[n.id]:(Vt.error("Could not find the correct cell for column at position "+e+".\n Make sure either 'id' or 'selector' is defined for all columns."),null)})})},r._process=function(t){return{data:this.castData(t.data),total:t.total}},e(n,[{key:"type",get:function(){return K.Transformer}}]),n}(tt),ln=/*#__PURE__*/function(){function t(){}return t.createFromConfig=function(t){var n=new rn;return t.storage instanceof Jt&&n.register(new sn({serverStorageOptions:t.server})),n.register(new on({storage:t.storage})),n.register(new an({header:t.header})),n.register(new un),n},t}(),cn=function(t){var n=this;this.state=void 0,this.listeners=[],this.isDispatching=!1,this.getState=function(){return n.state},this.getListeners=function(){return n.listeners},this.dispatch=function(t){if("function"!=typeof t)throw new Error("Reducer is not a function");if(n.isDispatching)throw new Error("Reducers may not dispatch actions");n.isDispatching=!0;var e=n.state;try{n.state=t(n.state)}finally{n.isDispatching=!1}for(var r,o=a(n.listeners);!(r=o()).done;)(0,r.value)(n.state,e);return n.state},this.subscribe=function(t){if("function"!=typeof t)throw new Error("Listener is not a function");return n.listeners=[].concat(n.listeners,[t]),function(){return n.listeners=n.listeners.filter(function(n){return n!==t})}},this.state=t},fn=function(t,n){var e={__c:n="__cC"+m++,__:null,Consumer:function(t,n){return t.children(n)},Provider:function(t){var e,r;return this.getChildContext||(e=[],(r={})[n]=this,this.getChildContext=function(){return r},this.shouldComponentUpdate=function(t){this.props.value!==t.value&&e.some(E)},this.sub=function(t){e.push(t);var n=t.componentWillUnmount;t.componentWillUnmount=function(){e.splice(e.indexOf(t),1),n&&n.call(t)}}),t.children}};return e.Provider.__=e.Consumer.contextType=e}(),pn=/*#__PURE__*/function(){function n(){Object.assign(this,n.defaultConfig())}var e=n.prototype;return e.assign=function(t){return Object.assign(this,t)},e.update=function(t){return t?(this.assign(n.fromPartialConfig(r({},this,t))),this):this},n.defaultConfig=function(){return{store:new cn({status:l.Init,header:void 0,data:null}),plugin:new $t,tableRef:{current:null},width:"100%",height:"auto",processingThrottleMs:100,autoWidth:!0,style:{},className:{}}},n.fromPartialConfig=function(e){var r=(new n).assign(e);return"boolean"==typeof e.sort&&e.sort&&r.assign({sort:{multiColumn:!0}}),r.assign({header:Kt.createFromConfig(r)}),r.assign({storage:Qt.createFromConfig(r)}),r.assign({pipeline:ln.createFromConfig(r)}),r.assign({translator:new Lt(r.language)}),r.plugin=new $t,r.search&&r.plugin.add({id:"search",position:t.PluginPosition.Header,component:Dt}),r.pagination&&r.plugin.add({id:"pagination",position:t.PluginPosition.Footer,component:Ft}),r.plugins&&r.plugins.forEach(function(t){return r.plugin.add(t)}),r},n}();function dn(t){var n,e=It();return x("td",r({role:t.role,colSpan:t.colSpan,"data-column-id":t.column&&t.column.id,className:rt(et("td"),t.className,e.className.td),style:r({},t.style,e.style.td),onClick:function(n){t.messageCell||e.eventEmitter.emit("cellClick",n,t.cell,t.column,t.row)}},(n=t.column)?"function"==typeof n.attributes?n.attributes(t.cell.data,t.row,t.column):n.attributes:{}),t.column&&"function"==typeof t.column.formatter?t.column.formatter(t.cell.data,t.row,t.column):t.column&&t.column.plugin?x(Gt,{pluginId:t.column.id,props:{column:t.column,cell:t.cell,row:t.row}}):t.cell.data)}function hn(t){var n=It(),e=Ht(function(t){return t.header});return x("tr",{className:rt(et("tr"),n.className.tr),onClick:function(e){t.messageRow||n.eventEmitter.emit("rowClick",e,t.row)}},t.children?t.children:t.row.cells.map(function(n,r){var o=function(t){if(e){var n=Kt.leafColumns(e.columns);if(n)return n[t]}return null}(r);return o&&o.hidden?null:x(dn,{key:n.id,cell:n,row:t.row,column:o})}))}function _n(t){return x(hn,{messageRow:!0},x(dn,{role:"alert",colSpan:t.colSpan,messageCell:!0,cell:new X(t.message),className:rt(et("message"),t.className?t.className:null)}))}function mn(){var t=It(),n=Ht(function(t){return t.data}),e=Ht(function(t){return t.status}),r=Ht(function(t){return t.header}),o=At(),i=function(){return r?r.visibleColumns.length:0};return x("tbody",{className:rt(et("tbody"),t.className.tbody)},n&&n.rows.map(function(t){return x(hn,{key:t.id,row:t})}),e===l.Loading&&(!n||0===n.length)&&x(_n,{message:o("loading"),colSpan:i(),className:rt(et("loading"),t.className.loading)}),e===l.Rendered&&n&&0===n.length&&x(_n,{message:o("noRecordsFound"),colSpan:i(),className:rt(et("notfound"),t.className.notfound)}),e===l.Error&&x(_n,{message:o("error"),colSpan:i(),className:rt(et("error"),t.className.error)}))}var vn=/*#__PURE__*/function(t){function n(){return t.apply(this,arguments)||this}o(n,t);var r=n.prototype;return r.validateProps=function(){for(var t,n=a(this.props.columns);!(t=n()).done;){var e=t.value;void 0===e.direction&&(e.direction=1),1!==e.direction&&-1!==e.direction&&Vt.error("Invalid sort direction "+e.direction)}},r.compare=function(t,n){return t>n?1:t1&&(f=!0,c=!0):0===a?c=!0:a>0&&!e?(c=!0,f=!0):a>0&&e&&(c=!0),f&&(s=[]),c)s.push({index:t,direction:n,compare:o});else if(d){var h=s.indexOf(l);s[h].direction=n}else if(p){var _=s.indexOf(l);s.splice(_,1)}return r({},i,{sort:{columns:s}})}},yn=function(t,n,e){return function(o){var i=(o.sort?[].concat(o.sort.columns):[]).find(function(n){return n.index===t});return r({},o,i?gn(t,1===i.direction?-1:1,n,e)(o):gn(t,1,n,e)(o))}},bn=/*#__PURE__*/function(t){function n(){return t.apply(this,arguments)||this}return o(n,t),n.prototype._process=function(t){var n={};return this.props.url&&(n.url=this.props.url(t.url,this.props.columns)),this.props.body&&(n.body=this.props.body(t.body,this.props.columns)),r({},t,n)},e(n,[{key:"type",get:function(){return K.ServerSort}}]),n}(tt);function wn(t){var n=It(),e=jt().dispatch,o=At(),i=gt(0),u=i[0],s=i[1],a=n.sort,l=Ht(function(t){return t.sort}),c="object"==typeof(null==a?void 0:a.server)?K.ServerSort:K.Sort,f=function(){var t=n.pipeline.getStepsByType(c);if(t.length)return t[0]};return yt(function(){var t=f()||(c===K.ServerSort?new bn(r({columns:l?l.columns:[]},a.server)):new vn({columns:l?l.columns:[]}));return n.pipeline.tryRegister(t),function(){return n.pipeline.unregister(t)}},[n]),yt(function(){if(l){var n,e=l.columns.find(function(n){return n.index===t.index});e?(0===u&&(e.direction=null!=(n=t.direction)?n:1),s(e.direction)):s(0)}},[l]),yt(function(){var t=f();t&&l&&t.setProps({columns:l.columns})},[l]),x("button",{tabIndex:-1,"aria-label":o("sort.sort"+(1===u?"Desc":"Asc")),title:o("sort.sort"+(1===u?"Desc":"Asc")),className:rt(et("sort"),et("sort",function(t){return 1===t?"asc":-1===t?"desc":"neutral"}(u)),n.className.sort),onClick:function(n){n.preventDefault(),n.stopPropagation(),e(yn(t.index,!0===n.shiftKey&&a.multiColumn,t.compare))}})}var xn=function(t,n){var e;void 0===n&&(n=100);var r=Date.now(),o=function(){r=Date.now(),t.apply(void 0,[].slice.call(arguments))};return function(){var t=[].slice.call(arguments),i=Date.now(),u=i-r;u>=n?o.apply(void 0,t):(e&&clearTimeout(e),e=setTimeout(function(){o.apply(void 0,t),e=null},n-u))}};function kn(t){var n,e=function(t){return t instanceof MouseEvent?Math.floor(t.pageX):Math.floor(t.changedTouches[0].pageX)},r=function(r){r.stopPropagation();var u=parseInt(t.thRef.current.style.width,10)-e(r);n=xn(function(t){return o(t,u)},10),document.addEventListener("mouseup",i),document.addEventListener("touchend",i),document.addEventListener("mousemove",n),document.addEventListener("touchmove",n)},o=function(n,r){n.stopPropagation();var o=t.thRef.current;r+e(n)>=parseInt(o.style.minWidth,10)&&(o.style.width=r+e(n)+"px")},i=function t(e){e.stopPropagation(),document.removeEventListener("mouseup",t),document.removeEventListener("mousemove",n),document.removeEventListener("touchmove",n),document.removeEventListener("touchend",t)};return x("div",{className:rt(et("th"),et("resizable")),onMouseDown:r,onTouchStart:r,onClick:function(t){return t.stopPropagation()}})}function Pn(t){var n=It(),e=bt(null),o=gt({}),i=o[0],u=o[1],s=jt().dispatch;yt(function(){if(n.fixedHeader&&e.current){var t=e.current.offsetTop;"number"==typeof t&&u({top:t})}},[e]);var a,l=function(){return null!=t.column.sort},c=function(e){e.stopPropagation(),l()&&s(yn(t.index,!0===e.shiftKey&&n.sort.multiColumn,t.column.sort.compare))};return x("th",r({ref:e,"data-column-id":t.column&&t.column.id,className:rt(et("th"),l()?et("th","sort"):null,n.fixedHeader?et("th","fixed"):null,n.className.th),onClick:c,style:r({},n.style.th,{minWidth:t.column.minWidth,width:t.column.width},i,t.style),onKeyDown:function(t){l()&&13===t.which&&c(t)},rowSpan:t.rowSpan>1?t.rowSpan:void 0,colSpan:t.colSpan>1?t.colSpan:void 0},(a=t.column)?"function"==typeof a.attributes?a.attributes(null,null,t.column):a.attributes:{},l()?{tabIndex:0}:{}),x("div",{className:et("th","content")},void 0!==t.column.name?t.column.name:void 0!==t.column.plugin?x(Gt,{pluginId:t.column.plugin.id,props:{column:t.column}}):null),l()&&x(wn,r({index:t.index},t.column.sort)),t.column.resizable&&t.index0?(Vt.error("The container element "+t+" is not empty. Make sure the container is empty and call render() again"),this):(this.config.container=t,q(this.createElement(),t),this)},n}(Q);t.Cell=X,t.Component=S,t.Config=pn,t.Grid=Ln,t.Row=Z,t.className=et,t.createElement=x,t.createRef=function(){return{current:null}},t.h=x,t.html=G,t.useConfig=It,t.useEffect=yt,t.useRef=bt,t.useSelector=Ht,t.useState=gt,t.useStore=jt,t.useTranslator=At}); -//# sourceMappingURL=gridjs.umd.js.map