Web dashboard v2 step 2: Tailwind design system, theme toggle, reader pages
Replaces the hand-written stylesheet with a Tailwind CSS v4 source (src/web/tailwind.css) whose minified build is committed as src/web/static/app.css and still embedded with include_str!, so the binary and the test suite never need Node. package.json pins tailwindcss and @tailwindcss/cli to 4.3.3 and adds css, css:watch and css:check. Colours are semantic CSS variables swapped per theme and exposed to Tailwind through @theme inline, so templates write bg-paper/text-ink/border-rule once and both themes work. theme.js runs synchronously in <head> to set data-theme before paint; app.js cycles the toggle system -> light -> dark and persists the choice. Newsreader is self-hosted and served from web::static_asset with the same ETag/304 logic as the other assets. Every reader template is restyled: ears row, centred masthead with its rules, tracked section labels, the drop cap on The Brief, "Why it's here" with its accent bar, the segmented rating control, prev/next cards, and the colophon as a two-column definition grid. Dashboard templates are untouched (step 3). Also fixes a .block collision with Tailwind's display utility, the SVG `hidden` property that never toggled the theme icon, and points the dev seed's profile_path at ./dev so previewing cannot rewrite data/profile.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
This commit is contained in:
+12
-2
@@ -116,7 +116,7 @@ fn story_article(index: usize, story: &(&str, &str, &str, &str, &str, i64)) -> A
|
||||
source.feed_id = article.feed_id;
|
||||
}
|
||||
article.content_html = body_html(title, words);
|
||||
if index % 3 != 0 {
|
||||
if !index.is_multiple_of(3) {
|
||||
article.social.clear();
|
||||
}
|
||||
if index == 4 {
|
||||
@@ -278,13 +278,23 @@ async fn seed_issue(db: &Db, issue: &mut Issue, with_snapshot: bool) -> anyhow::
|
||||
fn write_config(dir: &Path) -> anyhow::Result<PathBuf> {
|
||||
let path = dir.join("config.toml");
|
||||
let dir = dir.canonicalize()?;
|
||||
// The profile lives inside the throwaway directory: saving it from
|
||||
// /dashboard/profile must never rewrite the repository's data/profile.md.
|
||||
let profile = dir.join("profile.md");
|
||||
let toml = format!(
|
||||
"database_path = \"{db}\"\n\n[publish]\nepub_dir = \"{epubs}\"\nxtc_dir = \"{xtc}\"\n\n[server]\nbind = \"127.0.0.1:3599\"\npublic_url = \"http://127.0.0.1:3599\"\njobs_enabled = false\n",
|
||||
"database_path = \"{db}\"\nprofile_path = \"{profile}\"\n\n[publish]\nepub_dir = \"{epubs}\"\nxtc_dir = \"{xtc}\"\n\n[server]\nbind = \"127.0.0.1:3599\"\npublic_url = \"http://127.0.0.1:3599\"\njobs_enabled = false\n",
|
||||
db = dir.join("daily-epub.db").display(),
|
||||
profile = profile.display(),
|
||||
epubs = dir.join("epubs").display(),
|
||||
xtc = dir.join("xtc").display(),
|
||||
);
|
||||
std::fs::write(&path, toml)?;
|
||||
if !profile.exists() {
|
||||
let shipped = Path::new("data/profile.md");
|
||||
let body = std::fs::read_to_string(shipped)
|
||||
.unwrap_or_else(|_| "# Reader profile\n\n## Who he is\n\nA developer.\n".to_string());
|
||||
std::fs::write(&profile, body)?;
|
||||
}
|
||||
std::fs::create_dir_all(dir.join("epubs"))?;
|
||||
std::fs::create_dir_all(dir.join("xtc"))?;
|
||||
Ok(path)
|
||||
|
||||
Reference in New Issue
Block a user