fix: A1-10 write template file to disk on create instead of leaving file_path empty
This commit is contained in:
@@ -19,7 +19,7 @@ Gap categories: **SC** = spec correct, fix code | **CS** = code correct, update
|
|||||||
| A1-7 | ~~Template lookup must use all 4 levels (post→tag→category→default)~~ | template_context.allium:267-277 | `resolve_post_template_slug/3` implements tag→category cascade; all callers (preview, generation) updated | **Resolved:** `resolve_post_template_slug/3` in template_selection.ex, callers in preview.ex, router.ex, outputs.ex updated, 8 tests added |
|
| A1-7 | ~~Template lookup must use all 4 levels (post→tag→category→default)~~ | template_context.allium:267-277 | `resolve_post_template_slug/3` implements tag→category cascade; all callers (preview, generation) updated | **Resolved:** `resolve_post_template_slug/3` in template_selection.ex, callers in preview.ex, router.ex, outputs.ex updated, 8 tests added |
|
||||||
| A1-8 | ~~`ValidateLiquid`/`ValidateScript` before publish~~ | template.allium:110, script.allium:165 | `publish_template` validates Liquid via `Liquex.parse`, `publish_script` validates Lua via `BDS.Scripting.validate` | **Resolved:** validation gates added to `publish_template/1` and `publish_script/1`, invalid content returns `{:error, {:invalid_liquid|:invalid_script, reason}}`, 4 tests added |
|
| A1-8 | ~~`ValidateLiquid`/`ValidateScript` before publish~~ | template.allium:110, script.allium:165 | `publish_template` validates Liquid via `Liquex.parse`, `publish_script` validates Lua via `BDS.Scripting.validate` | **Resolved:** validation gates added to `publish_template/1` and `publish_script/1`, invalid content returns `{:error, {:invalid_liquid|:invalid_script, reason}}`, 4 tests added |
|
||||||
| A1-9 | ~~17 preset colors + custom hex in tag picker~~ | editor_tags.allium | `ColourPicker` hook + popover with 17 preset swatches grid and custom hex input, wired to both create and edit forms | **Resolved:** replaced native `<input type="color">` with `ColourPickerPopover` component (17 presets, custom hex #RRGGBB, immediate selection), JS hook for click-away dismiss, 1 test added |
|
| A1-9 | ~~17 preset colors + custom hex in tag picker~~ | editor_tags.allium | `ColourPicker` hook + popover with 17 preset swatches grid and custom hex input, wired to both create and edit forms | **Resolved:** replaced native `<input type="color">` with `ColourPickerPopover` component (17 presets, custom hex #RRGGBB, immediate selection), JS hook for click-away dismiss, 1 test added |
|
||||||
| A1-10 | Template file written on create | engine_side_effects.allium:151-153 | Draft templates have `file_path=""` | Fix code: write template file on create |
|
| A1-10 | ~~Template file written on create~~ | engine_side_effects.allium:151-153 | `create_template` now computes `file_path` and writes template file with YAML frontmatter on create | **Resolved:** `create_template/1` writes `templates/{slug}.liquid` on create, `next_template_file_path` always computes path, 1 test added |
|
||||||
| A1-11 | Graceful shutdown with inflight request tracking | preview.allium:47-48 | Kills acceptor process, no inflight tracking | Fix code: track inflight requests, drain before shutdown |
|
| A1-11 | Graceful shutdown with inflight request tracking | preview.allium:47-48 | Kills acceptor process, no inflight tracking | Fix code: track inflight requests, drain before shutdown |
|
||||||
| A1-12 | Real Pagefind integration for search | generation.allium:208 | Stub only: `pagefind-ui.js` is one-liner, `PagefindUI` never defined, search-runtime.js silently bails, client-side search non-functional | Fix code: bundle real Pagefind, build proper fragment index, wire PagefindUI |
|
| A1-12 | Real Pagefind integration for search | generation.allium:208 | Stub only: `pagefind-ui.js` is one-liner, `PagefindUI` never defined, search-runtime.js silently bails, client-side search non-functional | Fix code: bundle real Pagefind, build proper fragment index, wire PagefindUI |
|
||||||
| A1-13 | Git sidebar shows only "Working tree" placeholder | sidebar_views.allium:651-770 | `sidebar.ex:782-798` returns single entity_list item; `BDS.Git` has full status/diff/commit/history/fetch/pull/push/prune_lfs but sidebar doesn't use it | Fix code: wire sidebar `git_view/0` to `BDS.Git` — render branch, ahead/behind, status file list, commit input, history entries, action buttons per spec |
|
| A1-13 | Git sidebar shows only "Working tree" placeholder | sidebar_views.allium:651-770 | `sidebar.ex:782-798` returns single entity_list item; `BDS.Git` has full status/diff/commit/history/fetch/pull/push/prune_lfs but sidebar doesn't use it | Fix code: wire sidebar `git_view/0` to `BDS.Git` — render branch, ahead/behind, status file list, commit input, history entries, action buttons per spec |
|
||||||
|
|||||||
@@ -28,23 +28,38 @@ defmodule BDS.Templates do
|
|||||||
now = Persistence.now_ms()
|
now = Persistence.now_ms()
|
||||||
project_id = attr(attrs, :project_id)
|
project_id = attr(attrs, :project_id)
|
||||||
title = attr(attrs, :title) || ""
|
title = attr(attrs, :title) || ""
|
||||||
|
slug = unique_slug(project_id, Slug.slugify(title), "template")
|
||||||
|
file_path = template_file_path(slug)
|
||||||
|
|
||||||
|
changeset =
|
||||||
%Template{}
|
%Template{}
|
||||||
|> Template.changeset(%{
|
|> Template.changeset(%{
|
||||||
id: Ecto.UUID.generate(),
|
id: Ecto.UUID.generate(),
|
||||||
project_id: project_id,
|
project_id: project_id,
|
||||||
slug: unique_slug(project_id, Slug.slugify(title), "template"),
|
slug: slug,
|
||||||
title: title,
|
title: title,
|
||||||
kind: attr(attrs, :kind),
|
kind: attr(attrs, :kind),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
version: 1,
|
version: 1,
|
||||||
file_path: "",
|
file_path: file_path,
|
||||||
status: :draft,
|
status: :draft,
|
||||||
content: attr(attrs, :content),
|
content: attr(attrs, :content),
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_at: now
|
updated_at: now
|
||||||
})
|
})
|
||||||
|> Repo.insert()
|
|
||||||
|
with {:ok, template} <- Repo.insert(changeset) do
|
||||||
|
full_path = full_file_path(template.project_id, file_path)
|
||||||
|
File.mkdir_p!(Path.dirname(full_path))
|
||||||
|
|
||||||
|
:ok =
|
||||||
|
Persistence.atomic_write(
|
||||||
|
full_path,
|
||||||
|
serialize_template_file(template, template.content || "")
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, template}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_template(String.t()) :: Template.t() | nil
|
@spec get_template(String.t()) :: Template.t() | nil
|
||||||
@@ -348,7 +363,6 @@ defmodule BDS.Templates do
|
|||||||
Path.join(Projects.project_data_dir(project), relative_path)
|
Path.join(Projects.project_data_dir(project), relative_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp next_template_file_path(%Template{file_path: ""}, _next_slug), do: ""
|
|
||||||
defp next_template_file_path(%Template{}, next_slug), do: template_file_path(next_slug)
|
defp next_template_file_path(%Template{}, next_slug), do: template_file_path(next_slug)
|
||||||
|
|
||||||
defp serialize_template_file(template, content) do
|
defp serialize_template_file(template, content) do
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ defmodule BDS.TemplatesTest do
|
|||||||
assert template.status == :draft
|
assert template.status == :draft
|
||||||
assert template.enabled == true
|
assert template.enabled == true
|
||||||
assert template.version == 1
|
assert template.version == 1
|
||||||
assert template.file_path == ""
|
assert template.file_path == "templates/article-view.liquid"
|
||||||
assert template.content == "<article>{{ content }}</article>"
|
assert template.content == "<article>{{ content }}</article>"
|
||||||
|
|
||||||
assert {:ok, duplicate} =
|
assert {:ok, duplicate} =
|
||||||
@@ -43,6 +43,28 @@ defmodule BDS.TemplatesTest do
|
|||||||
assert duplicate.slug == "article-view-2"
|
assert duplicate.slug == "article-view-2"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "create_template writes template file to disk", %{project: project, temp_dir: temp_dir} do
|
||||||
|
assert {:ok, template} =
|
||||||
|
BDS.Templates.create_template(%{
|
||||||
|
project_id: project.id,
|
||||||
|
title: "My Layout",
|
||||||
|
kind: :post,
|
||||||
|
content: "<main>{{ content }}</main>"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert template.file_path == "templates/my-layout.liquid"
|
||||||
|
|
||||||
|
full_path = Path.join(temp_dir, template.file_path)
|
||||||
|
assert File.exists?(full_path)
|
||||||
|
|
||||||
|
contents = File.read!(full_path)
|
||||||
|
assert contents =~ "---\nid: #{template.id}\n"
|
||||||
|
assert contents =~ "slug: my-layout\n"
|
||||||
|
assert contents =~ "title: My Layout\n"
|
||||||
|
assert contents =~ "kind: post\n"
|
||||||
|
assert contents =~ "\n---\n<main>{{ content }}</main>\n"
|
||||||
|
end
|
||||||
|
|
||||||
test "publish_template writes a liquid file with frontmatter and clears draft content", %{
|
test "publish_template writes a liquid file with frontmatter and clears draft content", %{
|
||||||
project: project,
|
project: project,
|
||||||
temp_dir: temp_dir
|
temp_dir: temp_dir
|
||||||
|
|||||||
Reference in New Issue
Block a user