Skip to content

Releases: adamsalves/terminal-mono

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 14:51
93d9433

Fixed

  • v0.9.0 only built if you installed the theme as a Hugo module. It made
    the AEO module its own [[module.imports]], which reads as tidy and is
    wrong: Hugo resolves a module import only when the consuming project is
    module-based. With the theme sitting in themes/ as a directory — which is
    what a git submodule, a manual copy, --themesDir and both demo deploys all
    produce — Hugo looked for the import on disk under
    themes/github.com/adamsalves/aeo-hugo and failed the build outright. The
    GitHub Pages and Netlify demos stopped deploying, and so did
    hugo server --themesDir ../...

    The site declares the module now, not the theme. One more
    [[module.imports]], or one more submodule, and every installation path
    works the same way:

    # Hugo Modules
    [module]
      [[module.imports]]
        path = "github.com/adamsalves/terminal-mono"
      [[module.imports]]
        path = "github.com/adamsalves/aeo-hugo"
    # submodule or manual copy — aeo-hugo first, the array is precedence order
    theme = ["aeo-hugo", "terminal-mono"]

    This is a breaking change for a site that installed v0.9.0 as a Hugo
    module
    , which is why it is a minor and not a patch. That site had the AEO
    module transitively and never named it; upgrading without adding the import
    fails at config time, with a message that does not say why:

    ERROR failed to create config from modules config:
    unknown output format "llmsfull" for kind "home"
    

    The LLMS, LLMSFULL and MARKDOWN output formats come from the module, so
    the [outputs] block is left naming formats nothing defines. Add the second
    import and it builds. Sites on any other installation path were not building
    at all on v0.9.0, so there is nothing there to break.

    All five paths are now built in CI or by hand before release: modules,
    submodule, manual copy, --themesDir, and the two demo deploys.

Full changelog: v0.9.0...v0.10.0

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 14:28
9736309

Changed

  • AEO is a module now, not 816 lines of this theme. llms.txt,
    llms-full.txt, the markdown twins, robots.txt, the sitemap templates and
    the JSON-LD graph move to [aeo-hugo][aeo], which this theme imports — so a
    site using the theme gets all of it transitively and installs nothing. The
    templates were built and battle-tested here; they left because a second theme
    wanting the same thing had no way to get it without copying them, and because
    a bug fixed in one copy stays broken in the other. Ten files and the
    [outputFormats] block are gone from the theme; head.html calls
    aeo-schema.html and aeo-indexable.html instead of its own.

    The published output is unchanged: of the 118 files the exampleSite builds,
    116 are byte-identical to the pre-migration build, minified and not. The two
    that differ are llms.txt and pt/llms.txt, by one line each — this theme
    printed its [params.hero] subtitle under the description, and a component
    cannot know where a theme keeps its hero.

    Migrating a site: two config moves, both because the module reads its own
    namespace rather than this theme's params.

    • [params] allowIndexing[params.aeo] allowIndexing. The old spelling
      still works and warns once.

    • Publisher identity, which the theme used to read from [params.hero] subtitle and [params.footer.socialNetworks], is stated directly:

      [params.aeo.publisher]
        type = "Person"
        sameAs = ["https://github.com/you", "https://linkedin.com/in/you"]
      
      [languages.en.params.aeo.publisher]
        jobTitle = "Front-End Developer"

    A site that skips the second one keeps its whole JSON-LD graph and loses
    Person.jobTitle and Person.sameAs.

    The module warns at build time when it finds itself unwired — another
    component's robots.txt winning the theme array, or [outputs] missing —
    but only for a site that wrote a [params.aeo] table, so a site not using
    AEO hears nothing. ignoreLogs = ['aeo-no-llms'] silences one,
    [params.aeo] quiet = true all of them.

    The theme's README AEO section shrinks from 208 lines to 60: what stays is
    the [outputs] block a site must write itself, the publisher config, and
    where to read the rest. scripts/check_aeo.py stays — it asserts this
    theme's published output, which is worth checking whoever wrote the
    templates.

Full changelog: v0.8.0...v0.9.0

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 27 Aug 15:31
b32d7d4

Added

  • [params.hero] fuseSubtitle, which joins the name and the subtitle into a
    single <h1> — "Robin Vale — Studio" — and drops the .hero__role line under
    it. That pair is not new: head.html has always built it for the <title>
    tag, with the same em dash. What was new was that the page never showed it, so
    a theme whose browser tab read "Robin Vale — Studio" had a heading that read
    only "Robin Vale", and the h1 an answer engine or a search result quotes was
    the half without the role in it. The role line is dropped rather than kept
    beside the fused heading, because it renders that same subtitle and two copies
    of one string is not a layout.

    Only an explicit true turns it on. It reads through params-bool.html, so
    that is the same rule enable follows in sections.html rather than a second
    spelling of it: an absent key is off and a non-boolean warns instead of being
    guessed at. The switch also tests the subtitle before it stands, because the
    failure it can otherwise produce is a heading ending in a dash with nothing
    after it — and an absent subtitle is nil, which printf "%v" renders as the
    string <nil>, so the obvious guard is the one that lets it through. CI
    asserts both directions of the switch, the missing-subtitle case, the explicit
    false, and that a fused headline holds exactly one copy of the subtitle.

    The subtitle itself is guarded now that it reaches the <h1>. It is read in
    three places — the fused heading, the role line and the terminal's data-role
    — from one params-scalar.html call, so a table written there is warned about
    and dropped rather than printing map[a:1] into the element a search result
    quotes and typing it into the terminal besides.

  • [params.hero] tagline, one short line under the headline for what a
    visitor should know before scrolling — where you are, what you are open to. It
    works with either headline shape and emits no element at all when unset, so a
    site that does not set it gets no empty <div> where the line would be. It
    goes through params-scalar.html like every other value that reaches the page
    as text: a table written here warns and is dropped instead of aborting a build
    over one short line.

Changed

  • [params.hero] intro can now be removed, by writing it as "". It was
    read through default, which fires on an empty string, so an empty intro
    rendered the theme's "hi, my name is" and there was no way to drop the line at
    all. That was survivable while the h1 held a name; it stopped being survivable
    next to a fused headline, where "hi, my name is" introduces a name and a job
    title, which is not what anyone is called. isset separates the two states
    default cannot — absent still gets the theme's string, written-and-empty now
    emits no element rather than an empty one. Same shape latestPosts uses to
    keep a configured 0 from reading as unset. An intro written as a table warns
    and drops the line, the same way params-scalar.html treats every other value
    that reaches the page as text — it does not fall back to the theme's string,
    which is what an absent key means.

Fixed

  • A site with no [params.hero] subtitle no longer renders an empty role
    line.
    .hero__role was emitted unconditionally, so a site that never set a
    subtitle shipped <div class="hero__role">&gt; </div> — an orphan chevron
    under the name, with 18px of margin below it holding space for a line that had
    nothing in it. It was easy to miss while it was one configuration nobody was
    steered toward; fuseSubtitle gave that state a second way in, since a site
    that fuses without a subtitle falls back to exactly this shape, and "the name
    alone" has to mean the name alone. CI pins both routes into it.

  • The hero's bio no longer sits flush against a fused headline. .hero h1
    carries 8px of bottom margin, which was only ever half of a gap the .hero__role
    line finished with 18 more. Fused with no tagline under it — the shortest
    configuration the README documents — nothing was left to finish it, and the bio
    landed 8px under a 46px display heading. .hero h1 + .hero__bio restores the
    26px of whitespace both other shapes leave there; the adjacent sibling matches
    only when nothing rendered in between, which is precisely that case.

Full changelog: v0.7.1...v0.8.0

v0.7.1

Choose a tag to compare

@github-actions github-actions released this 26 Aug 12:01
a0eec4a

Fixed

  • The terminal traffic lights rendered as slivers on a phone. .dot declared
    a width, a height and border-radius:50%, but no flex-shrink:0 — and every
    bar that holds the dots is a flex row with a text sibling that runs long:
    .card__bar on the post and project cards, .term__bar in the hero, the 404.
    A path like ~/blog/testes-em-vue-na-prática-vitest-testing-library.md
    overflows the bar on a narrow screen, and flex hands the deficit out in
    proportion to each item's base size, so the dots surrendered their share of the
    width while keeping their height. On the exampleSite at 500px they measured
    6.03×11 instead of 11×11, and the longer the post title the flatter they got.
    .card__path already carries the overflow:hidden and the ellipsis that make
    it the item meant to absorb the shrink; now it is the only one that can.
    .lang-dot on the project cards was the same rule with the same omission and
    got the same fix before a long enough language label found it. CI asserts both
    now — a desktop build never shows this, and the stylesheet reads correct.

Full changelog: v0.7.0...v0.7.1

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 25 Aug 15:01
188756f

Added

  • [params.aeo] flatSitemap, which publishes /sitemap.xml as one flat
    <urlset> covering every language instead of Hugo's <sitemapindex>. On a
    multilingual site the index is a shape a crawler has to know to follow, and a
    good number do not: they read /sitemap.xml, take the <loc>s and audit those
    as pages — two XML files with no title, no structured data and no prose — while
    the site itself is never opened. npx aeo.js check v0.0.16 does exactly this,
    and measured against a bilingual portfolio it cost 12 of 100 points (Meta "80%+
    pages have titles", Schema "Article/WebPage", Citability "Structured lists") for
    content already in the build: the one post it never reached carries a
    BlogPosting node and a <ul>. The flat urlset keeps every hreflang
    alternate, and the per-language sitemaps are still built and served at their own
    URLs, so nothing that already indexed one starts 404ing.

    Off by default, for the reason allowTraining is on: /sitemap.xml is a
    published contract with every crawler that already knows the site. Inert on a
    single-language site, where Hugo builds no index in the first place. Like the
    other switches it must be a real boolean — a string warns and changes nothing.

    It is a whole-site param, not a per-language one: /sitemap.xml is one file, so
    it is read from the first language by weight and any other language that sets a
    different value is named in a warning. The README teaches
    [languages.<lang>.params…] for other params, and silence here would be exactly
    the failure params-bool.html exists to prevent.

Fixed

  • The README's price for keepQuotes was wrong. It said "about 150 bytes",
    uncompressed and unattributed; on the exampleSite home page it is 436 (11,762 →
    12,198) and 29 gzipped. Both numbers are documented now, and the snippet gained
    the [minify] parent table it needs to be pasted into a hugo.toml as written.

Full changelog: v0.6.0...v0.7.0

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 25 Aug 09:16
9d17000

Added

  • llms.txt, llms-full.txt and a markdown twin per post, as Hugo output
    formats. /llms.txt is the index an answer engine can read in one request instead
    of crawling — title, summary, and every post as a linked list with a line of
    context; /llms-full.txt is the content behind those links in one file, each post
    preceded by its canonical URL; and each post publishes an index.md next to its
    HTML. All three are per language: a bilingual site gets /llms.txt and
    /pt/llms.txt, each listing its own posts. llms.txt links to the twins rather
    than the HTML, which is what the spec asks for, and the canonical URL is the first
    line inside each twin so a citation that follows the link still knows where to
    point. Both use .RenderShortcodes — the markdown as written, headings and code
    fences intact, with the shortcodes resolved — rather than .Plain, which is what is
    left after throwing that structure away, or .RawContent, which hands the reader
    unrendered Hugo template syntax where the figure was supposed to be. Part of #34.

    All three honour [params.aeo] disallow and the same build condition robots.txt
    uses.
    A path excluded from crawlers whose full body sits in llms-full.txt is not
    excluded, and answer engines are the audience that key names — the exclusion has to
    reach the files written for them or it is not one. An excluded post is named nowhere,
    its body is nowhere, and its twin says why instead of carrying it. A build that is
    not for indexing publishes the files with the same answer robots.txt gives, which
    keeps the four from contradicting each other the way robots.txt's own comment warns
    about.

    Posts in llms-full.txt are separated by a --- post: <url> --- line rather than a
    bare ---. A thematic break is ordinary markdown that someone writes inside a post
    without thinking about this file at all, and it was indistinguishable from the line
    that separates two posts — so was the setext underline under a heading. The boundary
    now carries the answer to the question the split is being made to answer.

    Every label and heading is English in every language's copy. They are keys, not prose:
    a reader parsing /pt/llms.txt should not have to know the site is Portuguese to find
    the post list, and ## Blog translated is a section a parser written against the
    spec's example cannot find. The values carry the language and - Language: states
    which one. Half of it used to be translated, which was the worst of the two — neither
    parseable by key nor readable as prose — and CI now asserts the two copies use the
    same keys.

    Every value that reaches a line is normalised for it. These are lines in a plain-text
    file with no forgiving renderer behind them: a newline inside a title ends the list
    it is in, and a ]TIL: array[0], Reading [a spec] — closes the markdown link
    early and turns the rest of the item into something else. TrimSpace was covering
    the ends of the first of those.

    The theme defines the three formats; the site declares them, because Hugo's default
    config merge does not bring a theme's [outputs] into the site's. One exception, now
    documented and asserted: a site with _merge = "deep" does inherit them, and one
    that also declares its own [outputs] inherits the kinds it did not restate — which
    means a markdown twin of every page it has. The README says so and CI asserts every
    half: that the files appear when a site declares the block, that a site that never
    does still builds and keeps its RSS feed, and what a deep merge actually gets.

  • scripts/check_aeo.py counts what it was supposed to check rather than only what it
    managed to match. A list item whose link the parser cannot read is a problem now, not
    silence — a title carrying a ] broke the link it sat in, the regex stopped matching
    it, and the item was simply not verified: two of four posts were corrupt in the
    fixture that found this and the file still came back clean, because links in another
    section matched. A line that continues the item above is caught alongside the blank
    line that was already, and a twin that names a different page is compared on the whole
    path rather than its last segment.

  • scripts/check_aeo.py grew the other half of its job: every link in every
    llms.txt resolves to a file the build published, each language's index is rooted
    at the site rather than at its own language directory, and every markdown twin
    names its own page back. The link check found two of its own bugs while it was
    being written — a percent-encoded tag (tags/migração/ is linked as
    tags/migra%C3%A7%C3%A3o/) and the second language's index, whose - Home: is
    /pt/ while its links are rooted at /.

  • The AEO score is printed to the GitHub Pages job summary on every deploy, as
    information and never as a gate — with its two limits printed next to it, because
    the number is a floor rather than a measurement. npx aeo.js check scans
    new URL(target).origin, so for a project site published under a path it reads the
    host root, which belongs to no deploy of this theme; and its HTML checks require
    quoted attributes while hugo --minify emits valid unquoted HTML5, so canonical
    and JSON-LD read as absent whatever is on the page. The authoritative check is
    check_aeo.py, which now also runs on the bytes about to be deployed.

  • scripts/check_aeo.py reads every page that carries a graph, not the home and the
    posts only. The gate had been if "BlogPosting" in html, which left the lists, the
    taxonomies, the term pages and the whole WebPage branch unverified — a
    /blogs/index.html with every one of its blocks corrupted came back clean. Redirect
    stubs are skipped rather than read as pages that lost their JSON-LD, which is what a
    site with defaultContentLanguageInSubdir publishes at its root. --not-indexable
    works against a preview build now: it was checking for a Sitemap: line that the
    template correctly does not emit there, so the script's only preview mode rejected the
    theme's own output. An unknown flag exits 2 instead of being accepted in silence, and
    the usage line names the flags the code actually reads.

  • layouts/robots.txt. Hugo's built-in one is User-agent: * and nothing else — no
    Sitemap: line, and nothing said either way about the crawlers that feed answer
    engines. This one names them, in two groups that are not the same request: answer
    engines fetch a page to answer a question now and cite the source back to the reader,
    and dataset crawlers collect it into a corpus with no citation and no referral.
    [params.aeo] allowAI and allowTraining switch them independently, both defaulting
    to true — which is what the bare User-agent: * already meant, so an upgrade does
    not quietly change what a site publishes. [params.aeo] disallow keeps paths out of
    every group, the AI ones included: robots.txt groups do not inherit, so a path excluded
    only from * would have stayed open to exactly the crawlers a site had just named.
    A build that is not for indexing publishes Disallow: / instead, matching the noindex
    meta head.html already emits — the two files disagreeing is how a deploy preview gets
    crawled. Requires enableRobotsTXT = true in the site's config: it is a root key and
    a theme's config is not merged for it. Part of #34.

  • JSON-LD for what the theme actually renders. Person (or Organization) and
    WebSite on every page, BlogPosting on a post, WebPage on any other single page,
    and BreadcrumbList on everything but the home page. Before this the site emitted a
    Person on the home page and nothing anywhere else — a blog whose posts never said
    they were posts, which is where the 0/20 on Schema Presence came from. The nodes are
    linked rather than repeated: the publisher carries an @id and the post's author and
    publisher point at it. BlogPosting carries headline (capped at the 110 characters
    Google's documentation caps it at, since a longer one drops the field entirely),
    datePublished, dateModified, author, image, keywords from the page's tags,
    wordCount and inLanguage. Breadcrumbs are built from .Ancestors — the real content
    tree, not the URL string — so a crumb cannot point somewhere that is not a page.
    The 404 is the one page that emits none: it is not in the content tree, so a
    breadcrumb there describes a hierarchy that does not contain it, and a WebSite node
    invites a crawler to treat an error as a document. A section index, a tag list
    and a term page carry a CollectionPage — the narrower true statement about a page
    whose content is the set of pages it links to, and the node their BreadcrumbList
    needed: without it those pages published a trail leading to something the graph said
    nothing about.

    Every string that reaches the graph is plain text, and getting there took two passes.
    truncate escapes a plain string and leaves a template.HTML alone, so headline
    the one field the theme transforms rather than copies — came out as
    Vue &amp;amp; Vitest for a title as ordinary as Vue & Vitest: HTML entities inside a
    JSON string, where the consumer reads them literally, contradicting the name built
    from the same title in the same node. name had the opposite problem, carrying
    whatever markup the front matter wrote. Both are plainified now, in the breadcrumb
    trail too, and check_aeo.py asserts the invariant that catches either drifting again:
    headline and name come from one title, so one has to be the start of the other.
    The 110-character cap was also 111 in practice — truncate appends its ellipsis
    after the limit — which the theme's own checker rejected. CI now builds a post whose
    title carries an ampersand, an apostrophe, markup, a quote, an emoji and 118
    characters, because every title in the...

Read more

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 21 Aug 18:46
9fec5d5

Fixed

  • Hero terminal: the section switch reaches the terminal. v0.4.0 taught the hero
    that its "view projects" button must not point at a section that does not
    render, but the terminal directly above it went on typing ls projects/ and
    listing the repositories of a section the reader could not scroll to. Both
    remaining commands now follow the plan: ls projects/ follows the projects
    section, and cat stack.txt follows about, which is where the skills live.
  • Hero terminal: a command with no output is no longer typed at all. Only the
    blog listing had ever followed that rule; cat stack.txt and ls projects/
    were unconditional, so a site that filled nothing in — the one CI has been
    building all along — greeted its reader with two commands and two blank lines
    under them. This half is fixed in the script rather than the template, so an
    empty value produces no command whatever put it there. whoami is the stated
    exception and always runs: its output is the page's own identity, not a
    section's data, so no switch can empty it.
  • Hero terminal: the reserved height counts the rows the text takes, not the
    lines it is written in. --hero-lines was a count of logical lines, and
    .term__body wraps — so on a 360px phone five of the exampleSite's fifteen
    lines take two rows each, the box reserved 369px for 480px of text, and it grew
    under the reader line by line as the animation typed: the exact shift the
    reservation exists to prevent, on the viewport where it is scored hardest. Only
    the browser knows the box's width and the font's advance, so terminal.js now
    measures both and writes the corrected count back before the first character is
    typed — and again whenever the box changes size or JetBrains Mono arrives under
    font-display:swap. Width is counted in terminal cells rather than characters,
    so a full-width glyph costs the two it really takes: a Japanese subtitle would
    otherwise model at half its width and the box would come out short again, which
    is this bug wearing the fix as a disguise. The blinking cursor is counted too —
    it belongs to no line of the script, and the closing prompt is where it comes to
    rest. What the template emits is unchanged and becomes the floor: the number a
    reader gets before the script runs, or without it. Not a regression from the
    entry below: v0.4.0 reserved the identical height by a different route and
    carried the same pre-wrap, so this is older than both.
  • Hero terminal: the reserved height follows the commands that actually render.
    It was a constant 12 lines plus one per post, which was right only while all
    three commands always rendered; drop one and the box reserved three lines it
    never filled, leaving a block of dead space under the hero — the same class of
    bug as section--last sitting on a section that was no longer last. The
    template now counts the lines it is about to emit and passes them as
    --hero-lines, replacing the --hero-posts variable and the
    .term__body--posts class, which are gone. CI recomputes the count from the
    rendered data-* attributes and holds the variable to it.
  • Config: a param written as the wrong type no longer takes the build down.
    sections.html has always stated the rule in its header — no configuration
    may fail the build — and normalised the four section tables to keep it. But
    every partial that read site.Params.<x> straight bypassed that, so the
    promise held in the one file that made it and nowhere else. Thirteen
    configurations aborted a build: [params.hero], [params.contact],
    [params.footer] and [params.navbar] written as scalars;
    [params.footer.socialNetworks], [params.hero.socialLinks] and
    [params.about.skills] the same one level down; items written as a scalar
    under about.skills, projects and experience; a jobs list inside an
    experience entry; and a list of bare strings where projects, experience or
    socialLinks expect a list of tables. Each now warns, names the param, and
    falls back.
    Four of the thirteen broke every page, not the home alone — head.html,
    footer.html and nav.html run site-wide, so a scalar hero, contact,
    footer or navbar took the 404 and every blog post with it. navbar was
    the sharpest of them: nav.html is the file the invariant names, and
    sections.html already guarded that same param for its showBlog gate, so
    the theme disagreed with itself about [params.navbar] in two files.
  • Config: three new partials carry that — params-table.html,
    params-list.html and params-scalar.html — and every consumer reads through
    them. A guard on a parent says nothing about its children, so nested tables go
    through the same helper; that is the whole reason six of the thirteen existed.
    Lists are tested for being lists rather than for truth: first does not
    reject a string, it slices its bytes, so items = "Go" would have rendered
    71 · 111 instead of failing anywhere a consumer could see it. Entries
    dropped from a list are counted in the warning, so a typo that silently
    removes one project from six is reported rather than simply absent — and
    entries are checked in both directions now, so a table written where a bare
    skill belongs is dropped and counted rather than rendering map[a:1] into a
    skill chip.
  • Config: the mirror direction is covered too — a scalar-shaped param written as
    a table or a list. It fails differently and had to be guarded separately:
    these never reach a range or a field lookup, they reach plainify,
    relURL, absURL, markdownify or urlize, all of which cast to string and
    abort when the cast fails. Eight more configurations, on top of the thirteen
    above: [params.description], [params.favicon] and [params.ogImage], read
    in head.html, so all three took whole sites down; a post's own image, from
    its front matter, in head.html for og:image and again in single.html for
    the featured banner; and the prose fields — [params.about] content, a
    project entry's content and title, and an experience job's content.
    The per-entry ones name the entry in the warning, by repo or by title once
    the title itself has been through the guard, so a list of six says which one.
    A param that is only ever printed is left alone on purpose: map[a:1] on the
    page is wrong but does not stop the build, and warning about it would report
    the same mistake twice for the reads that do go through a cast.
  • Config: gate 3 type-checks [params.about] content for the same reason it
    type-checks the lists — a table there is truthy, so it admitted the section
    and then rendered it as an empty shell.
  • Config: a warning about an experience entry with no company no longer
    degrades into printf's own error syntax. %q on a nil printed the warning as
    items %!q(<nil>) jobs must be a list, turning the half that names the broken
    entry into noise. CI now watches the warnings for printf garbage as well as
    the pages, since the log is the only place this one could ever appear.
  • Config: gate 3 in sections.html now type-checks the list-shaped params as
    well as testing them for emptiness. A scalar items is truthy, so it used to
    pass the gate and reach the partial; with the partials guarded the build
    survives, but the section would render with nothing in it — the defect v0.4.0
    closed. The warning has to come from the gate for the same reason: once the
    gate drops the section its partial never runs, so a guard that only warned
    inside the partial would go quiet exactly when the config is wrong.
  • SEO: a site that never set [params.hero] subtitle no longer publishes
    <title>Site — %!s(&lt;nil>)</title>. Go's printf has no nil case for
    %s, so the missing param was formatted straight into the page — on the home
    page of every site that skipped it, including the bare site CI has been
    building all along. Nothing warned, so --panicOnWarning could not see it and
    the build stayed green. The title now falls back to the site name alone. CI
    asserts no built page contains printf's error syntax, across every fixture.
  • SEO: jobTitle and description are omitted from the JSON-LD when unset
    instead of emitted as null. jsonify renders a nil as valid JSON, so this
    was never the defect above — but "jobTitle":null asserts that the person has
    no job title, where saying nothing asserts only that this site left the field
    empty.

Changed

  • Hero terminal: cat stack.txt now follows [params.about.skills] enable,
    which has to be an explicit true — an absent key counts as off, the same
    reading the skills block inside the about section and the section index have
    always used. The switch used to hide that block while the terminal above kept
    announcing the same list — one switch that meant two different things
    depending on where you looked. Sites that write enable = true, the
    exampleSite among them, are unaffected. A site that lists items without
    an enable key does lose cat stack.txt: it was already not rendering the
    skills block, and the terminal now agrees with the page instead of announcing
    a list the reader cannot find.
  • Docs: RELEASING.md now says what 0.x does with a breaking change. The
    SemVer table had no row for it, and the major row's example read 0.3.0
    1.0.0 — which taken literally made v0.4.0, breaking by the table's own
    definition, a 1.0.0. It was cut as 0.4.0 deliberately, per
    SemVer §4: while the theme is 0.x a
    breaking change goes in the minor, and 1.0.0 is reserved for the deliberate
    statement that the contracts are stable. The major row now counts from
    1.0.0 so it stops implying otherwise. Wording only — the script chooses
    nothing, the number is still yours to pass.

Full changelog: https://github.com/adamsalves/terminal-mono...

Read more

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 20 Aug 13:20
36bc07f

Added

  • Sections: [[menu.main]] now drives the order the home page renders its
    sections in, not just the nav. The menu is the page's index — a reader who
    sees "projects · about · experience" at the top expects the page in that
    order — and keeping two lists that are allowed to disagree is an invitation
    for them to. Delete an entry and the section goes with the link, which is the
    answer to "I don't want the experience section" that used to require
    overriding a layout in the consuming site. Non-section identifiers (blog,
    external links) stay nav-only, and the hero stays the page header rather than
    a section, so it cannot be moved or removed.
  • Sections: every section takes an enable switch — [params.about] enable,
    [params.projects], [params.experience], [params.contact]. It is a veto
    and never a summons: false removes the section and its nav link whatever the
    menu says, true grants nothing the menu and the section's own content do not
    already grant. Forcing inclusion would rebuild the two defects this release
    closes — a section outside the index, and a section rendered empty. Being an
    ordinary param, it is language-scoped: a section can be on in one language and
    off in the other without a second menu.
  • Sections: [menu.main.params] showInNav = false keeps a section on the page
    and takes only its link out of the nav, and [params.sections] order = [...]
    is the escape hatch for a site that wants the nav and the page in different
    orders on purpose. Order resolves as params.sections.order, then the menu,
    then the previous default.
  • Sections: no configuration can fail a build. Every misconfiguration warns
    and falls back — an unknown section name, a duplicate, an order that is not a
    list or is empty, params.sections or [params.<section>] written as
    something other than a table, and enable or showInNav set to something that
    is not a boolean (enable = "false" is a string, and used to be read as "on"
    in silence). A menu entry whose url is the wrong anchor for the section it
    names — url = "#sobre" on identifier = "about" — warns and has its link
    pointed at the right anchor, because the section's id is fixed by the theme and
    the typo has exactly one possible fix. One naming a section but linking
    somewhere else entirely (a page, an external URL) warns and is left alone: that
    may well be a real destination.
  • Sections: the theme warns when a section has content configured, is not turned
    off, and nothing in the index renders it — naming the section and the two ways
    to resolve it. This is the shape of a [[menu.main]] written for v0.3.0, where
    the menu drove only the nav: such a menu can now leave a home page with no
    sections at all, and this warning is what keeps that from happening quietly.
    [params.<section>] enable = false states that the omission is deliberate and
    silences it; so does deleting the section's params.

Fixed

  • Sections: a site that fills nothing in no longer ships links to sections that
    are not there. Each section decided to exist a different way — projects
    behind a with, experience behind enable, about and contact behind
    nothing at all — so an unconfigured site rendered about and contact as
    empty shells (heading, rule, nothing) while the nav offered #projects and
    #experience, two anchors that scrolled nowhere and announced normally to a
    screen reader. A fourth dead link, the hero's own "view projects" button, went
    the same way. All four now answer to one resolution, and CI walks every page of
    every build asserting that no link points at an anchor that is not on the page
    it targets — run against the previous release's bare output, that check reports
    17 dead anchors.
  • Sections: section--last follows the last section that actually renders. It
    was hardcoded onto contact, which was only correct while contact was
    guaranteed to be last; with contact removed or reordered, the page lost the
    96px of breathing room at its end.
  • Nav: a site with no links and one language no longer ships a hamburger button
    and an empty mobile menu for it to open.
  • Release: the branch cleanup no longer reports failure for a branch that is
    already gone. GitHub answers a delete of an absent ref with 422 "Reference
    does not exist", not 404, and only 404 was mapped to the already-gone case —
    so every release on a repo that deletes the head branch on merge printed
    "remove it by hand" for a branch the merge had already removed. v0.3.0 did.
    Nothing ever accumulated on the remote — the merge had done the work; only
    the report was wrong. The allowance is scoped to the caller that asks for it,
    so an unexplained 422 stays fatal everywhere else.

Changed

  • Sections: the menu now moves and removes the sections. A site that adopted
    [[menu.main]] in v0.3.0 — where the menu drove only the nav, and the README
    said so — will see its sections move on upgrade if its nav order differs from
    the layout order, and lose any section the menu does not name. A menu
    written for the nav alone, listing say about and an external link, now leaves
    the home page with one section instead of four; one naming no section at all
    leaves it with none. Both cases warn, naming each section that went missing.
    Set [params.sections] order = [...] to pin the previous layout and section
    set; the nav keeps following the menu.
  • Sections: the four section partials (about, projects, experience,
    contact) now expect a context of dict "last" <bool> and no longer decide
    for themselves whether to render — sections.html does. A site that overrode
    layouts/index.html and calls them with the page ({{ partial "about.html" . }})
    has to pass the dict instead, or read the plan the way the theme's own
    index.html does.
  • Experience: the section is now opt-out like the other three, where it used to
    be opt-in. [params.experience] enable was the only switch of its kind in the
    theme, and making all four consistent meant picking one default for all of
    them; a section that has items filled in and no explicit enable now
    renders rather than staying hidden. If that is your config and you want it
    hidden, set enable = false. Sites that already set enable = true — the
    exampleSite among them — are unaffected: their minified output is byte-identical.

Full changelog: v0.3.0...v0.4.0

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 13 Aug 09:52
d645dbc

Added

  • Hero: the terminal lists the newest posts as a fourth command, ls ~/blog --latest,
    with each filename linking to its post. The home page gave no sign a blog existed
    unless you read the nav; this surfaces it without adding a section or touching the
    order of the existing ones. It appears on its own once a language has posts, and
    the names are built from post titles so they follow the reader's language rather
    than the file on disk — post.md and post.pt.md would otherwise both read as
    English. Ordered by date newest-first regardless of weight. Each post's title is
    the link's accessible name. params.hero.latestPosts sets how many (0 disables),
    per site or per language, and prefers-reduced-motion gets the links immediately.
  • Nav: the menu order is configurable through Hugo's native [[menu.main]], sorted by
    weight, instead of being hardcoded in the partial. Labels are translated from each
    entry's identifier, so one block serves every language — an explicit name wins
    when you want a literal label. Anchors resolve against the current language's home, so
    they keep working from inside a blog post, and pageRef keeps internal links on the
    right language. Sites without [[menu.main]] render exactly as before: the default
    order is the fallback, and the minified output is byte-identical. The blog entry stays
    conditional on the language having posts, params.navbar.showBlog still overrides, and
    desktop and mobile now render from a single partial so they cannot drift apart.

Changed

  • Blog cards: the ~/blog/….md label follows the reader's language. It came from
    the file on disk, and post.md and post.pt.md collapse to one name, so every
    language showed the English one. It is built from the post title now, sharing
    partials/post-filename.html with the hero listing so the two cannot disagree.
    A long name is ellipsized rather than wrapping into the reading time.
  • Release: scripts/release.py now deletes the release/vX.Y.Z branch from the
    remote once the tag is pushed. It already removed the local copy, so the remote
    one accumulated — one orphan per release, as v0.2.3 left behind. The cleanup
    runs after the release is complete and only reports if it fails, since a
    leftover branch is litter rather than a broken release.

Full changelog: v0.2.3...v0.3.0

v0.2.3

Choose a tag to compare

@github-actions github-actions released this 12 Aug 13:25
496ccab

Added

  • Release automation: scripts/release.py cuts a release end to end (changelog
    promotion, release PR, CI gate, merge, annotated tag), and a release.yml
    workflow publishes the GitHub Release from the changelog section on tag push.
    A tag and a GitHub Release are separate objects and the sidebar reads the
    Release — v0.1.0 and v0.2.2 were both tagged without one. RELEASING.md
    documents the process, including recovery when a step fails.
  • Tests: scripts/test_release.py covers the logic that decides what gets
    published — changelog promotion, note extraction, tag ordering, token scoping
    and the CI gate — against the real CHANGELOG.md. ci.yml runs it on every
    pull request, so a release PR validates its own promoted changelog before it
    can merge. Tooling that merges to main and pushes tags should not be the
    one part of the repository nothing checks.

Changed

  • Docs: images/screenshot.png and images/tn.png are now captures of the bundled
    exampleSite/ — what the demo and the theme gallery actually serve — instead of a
    personal site with unrelated branding and content. Both were also recaptured without
    the browser scrollbar that had been baked into them, and the README image now uses a
    relative path so it resolves outside github.com too.

Full changelog: v0.2.2...v0.2.3