# A Hobonichi planning library — goals, habits, pens and spreads to try

Recipe No. 60, Hobbies and play. From The know.sh Cookbook: https://know.sh/cookbook/hobonichi-planner

Your Techo holds the days; this library holds the plan behind them. The year’s goals sit on one chart, your habits shade in day by day, every pen and ink has its notes, and the spreads you want to try wait on cards until a quiet Sunday.

- For: a Hobonichi Techo user who plans the year on paper
- You get: the year’s goals, a habit tracker, a pen log and spreads to try
- Time: an evening to set up, minutes a week
- Made with: Shelf, Document, Section, Elements, Your AI assistant

## How it works

1. **Set out the year’s goals.** Tell your assistant this year’s goals and when each starts and ends. Ask for a Gantt chart element in a Year document, with the finished ones marked done.
2. **Shade in your habits.** Each Sunday, read out the week from your habit strip: how many of your five habits you kept each day. Ask for a calendar element, with the days that changed things marked.
3. **Keep the pen and ink log.** Ask for a table element of pens and inks: nib, ink, how it dries and what shows through. Swatch on a spare page, then correct the table from what you see.
4. **File spreads to try.** Describe a spread you saw or dreamed up and ask for a card element: where it goes, which pens, how long it takes. Link a photo of it; never upload one.

## Try this prompt

Your assistant, connected to know.sh (Claude, ChatGPT or a local model):

> In my know.sh document 2026 in the Techo, add a calendar element of my habits: one value a day from 1 June, the number of my five habits I kept, from the list I give you. Mark the days I changed something, with a short note on each, and say in the source where the counts come from.

Your assistant, connected to know.sh (Claude, ChatGPT or a local model):

> Add a widget element to my Techo shelf: a daily page planner for a Hobonichi Original, A6, with a 3.7 mm grid. Let me share out rows between to-do, notes, journal and sketch with + and − buttons, draw the page, and say how many rows are left. Keep it plain and say in the caption what it simplifies.

## Elements in the specimens

Written as your assistant writes them through the know.sh MCP server. Each reads as plain words until you turn elements on (Account → Elements).

Calendar, specimen 1:

```element calendar
{
  "caption": "Habits kept each day, of five, June to September",
  "start": "2026-06-01",
  "values": "2, 3, 2, 2, 2, 2, 3, 3, 4, 3, 3, 3, 0, 3, 3, 3, 1, 1, 2, 1, 2, 4, 4, 3, 4, 4, 2, 4, 4, 5, 3, 3, 3, 3, 3, 4, 3, 3, 3, 5, 2, 3, 4, 2, 4, 5, 2, 3, 3, 3, 4, 4, 2, 4, 3, 4, 5, 4, 4, 2, 4, 2, 2, 2, 3, 3, 5, 2, 1, 3, 0, 0, 0, 5, 4, 1, 1, 4, 3, 3, 4, 5, 3, 3, 5, 5, 5, 5, 5, 2, 5, 5, 5, 2, 4, 5, 2, 3, 5, 3, 5, 5, 4, 4, 4, 4, 5, 4, 4, 5, 3, 3, 5, 5, 4, 3, 4, 3",
  "units": "habits",
  "steps": 5,
  "range": "zero",
  "marks": [
    {
      "date": "2026-06-22",
      "label": "The walk moves to before work"
    },
    {
      "date": "2026-08-10",
      "label": "Flu, three days"
    },
    {
      "date": "2026-08-24",
      "label": "The phone charges outside the bedroom"
    }
  ],
  "source": "Counted from the habit strip on each daily page, 1 June to 26 September."
}
```

Gantt chart, specimen 1:

```element gantt
{
  "caption": "The year’s goals, January to December",
  "description": "Filling every daily page runs all year, as does reading twenty-four books. A copperplate course ran February to April and is done. A sketch a week runs from March to December. The Lisbon trip journal was 12 to 19 May, done. The ink swatch book runs September and October. Setting up the 2027 book is a milestone on 20 December.",
  "width": "wide",
  "dateFormat": "YYYY-MM-DD",
  "sections": [
    {
      "id": "all",
      "label": "All year"
    },
    {
      "id": "make",
      "label": "Projects"
    }
  ],
  "tasks": [
    {
      "id": "pages",
      "label": "Every daily page",
      "section": "all",
      "shape": "bar",
      "start": "2026-01-01",
      "end": "2026-12-31",
      "status": "active"
    },
    {
      "id": "books",
      "label": "Twenty-four books",
      "section": "all",
      "shape": "bar",
      "start": "2026-01-01",
      "end": "2026-12-31",
      "status": "active"
    },
    {
      "id": "sketch",
      "label": "A sketch a week",
      "section": "all",
      "shape": "bar",
      "start": "2026-03-02",
      "end": "2026-12-31",
      "status": "active"
    },
    {
      "id": "copper",
      "label": "Copperplate course",
      "section": "make",
      "shape": "bar",
      "start": "2026-02-02",
      "end": "2026-04-27",
      "status": "done"
    },
    {
      "id": "lisbon",
      "label": "Lisbon trip journal",
      "section": "make",
      "shape": "bar",
      "start": "2026-05-12",
      "end": "2026-05-19",
      "status": "done"
    },
    {
      "id": "swatch",
      "label": "Ink swatch book",
      "section": "make",
      "shape": "bar",
      "start": "2026-09-01",
      "end": "2026-10-31",
      "status": "active"
    },
    {
      "id": "next",
      "label": "Set up 2027",
      "section": "make",
      "shape": "milestone",
      "start": "2026-12-20",
      "duration": "0d"
    }
  ],
  "axisFormat": "%b",
  "tickInterval": "1month",
  "weekStart": "monday",
  "weekend": "saturday",
  "today": "shown"
}
```

Table, specimen 2:

```element table
{
  "caption": "Pens and inks, tested on the Techo’s Tomoe River paper",
  "columns": [
    {
      "label": "Pen and tip",
      "align": "left"
    },
    {
      "label": "Ink",
      "align": "left"
    },
    {
      "label": "On the page",
      "align": "left"
    }
  ],
  "rows": [
    [
      "Juice Up 0.4",
      "Black gel",
      "To-dos: crisp, dry in 8 s"
    ],
    [
      "Kakuno F",
      "Iroshizuku Kon-peki",
      "Everyday; ghosts a little"
    ],
    [
      "Preppy 03",
      "Carbon Black",
      "Under watercolour, once dry"
    ],
    [
      "Micron 01",
      "Pigment black",
      "Sketch lines, the finest"
    ],
    [
      "Safari EF",
      "Yama-dori",
      "Headings: wet, slow, lovely"
    ]
  ],
  "pick": 0,
  "notes": "Pilot Juice Up and Kakuno, Platinum Preppy, Sakura Pigma Micron, Lamy Safari. The Juice Up is the pick for lists: it is dry before the page turns."
}
```

Card, specimen 2:

```element card
{
  "label": "Spread to try: a weather and mood strip",
  "aside": "Monthly pages",
  "rows": [
    {
      "term": "Where",
      "value": "The margin beside the monthly calendar, one square a day"
    },
    {
      "term": "Pens",
      "value": "Micron 01 for the weather sign, Kon-peki to shade the mood"
    },
    {
      "term": "Takes",
      "value": "Twenty minutes to set up, a few seconds a day"
    },
    {
      "term": "From",
      "value": "A photo I saved in March, linked in the notes"
    }
  ],
  "note": "Try it in October; if the squares stay empty by the 10th, drop it."
}
```

Widget, specimen 3:

```element widget
{
  "title": "Plan a daily page",
  "caption": "Choose the Original (A6) or the Cousin (A5), start from a work, sketch or travel day, then give each block more or fewer rows of the 3.7 mm grid and see the page fill. Margins, the date line and the quote line are simplified, so the row count is close, not exact.",
  "height": 480,
  "html": "<div class=\"techo\">\n  <div class=\"bar\">\n    <div class=\"seg\" role=\"group\" aria-label=\"Format\">\n      <button type=\"button\" data-f=\"a6\" aria-pressed=\"true\" aria-label=\"Original, A6\"><span class=\"long\">Original, A6</span><span class=\"short\">A6</span></button>\n      <button type=\"button\" data-f=\"a5\" aria-pressed=\"false\" aria-label=\"Cousin, A5\"><span class=\"long\">Cousin, A5</span><span class=\"short\">A5</span></button>\n    </div>\n    <div class=\"seg\" role=\"group\" aria-label=\"Start from a day\">\n      <button type=\"button\" data-p=\"work\" aria-pressed=\"true\" aria-label=\"Work day\"><span class=\"long\">Work day</span><span class=\"short\">Work</span></button>\n      <button type=\"button\" data-p=\"sketch\" aria-pressed=\"false\" aria-label=\"Sketch day\"><span class=\"long\">Sketch day</span><span class=\"short\">Sketch</span></button>\n      <button type=\"button\" data-p=\"travel\" aria-pressed=\"false\" aria-label=\"Travel day\"><span class=\"long\">Travel day</span><span class=\"short\">Travel</span></button>\n    </div>\n  </div>\n  <div class=\"body\">\n    <figure class=\"page\"><svg id=\"pg\" class=\"pg\" role=\"img\" aria-label=\"The planned page\" preserveAspectRatio=\"xMidYMid meet\"></svg></figure>\n    <div class=\"side\">\n      <ul class=\"zones\" id=\"zones\"></ul>\n      <label class=\"hrs\"><input type=\"checkbox\" id=\"hours\" checked> Hours down the left edge</label>\n      <p class=\"say\" id=\"say\" aria-live=\"polite\"></p>\n    </div>\n  </div>\n</div>",
  "css": ".techo { font: 400 14px/1.4 var(--sans); color: var(--ink); }\n.bar { display: flex; flex-wrap: wrap; gap: 8px 14px; }\n.short { display: none; }\n.seg { display: flex; }\n.seg button + button { border-left: 0; }\nbutton { font: 500 13.5px/1 var(--sans); color: var(--ink); background: var(--paper); border: 1px solid var(--ink); border-radius: 0; padding: 9px 11px; min-height: 38px; cursor: pointer; touch-action: manipulation; }\nbutton[aria-pressed='true'] { background: var(--ink); color: var(--paper); }\nbutton:focus-visible, input:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }\n.body { display: flex; gap: 22px; margin-top: 16px; align-items: flex-start; }\n.page { margin: 0; flex: 0 0 272px; }\n.pg { display: block; width: 100%; height: auto; overflow: visible; }\n.side { flex: 1; min-width: 0; }\n.zones { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--ink); }\n.zones li { display: flex; align-items: center; gap: 6px; border-bottom: 1px solid var(--rule); padding: 3px 0; }\n.zones .nm { flex: 1; font: 400 16px/1.2 var(--serif); }\n.zones .nm small { display: block; font: 400 12px/1.3 var(--sans); color: var(--ink-3); }\n.zones button { min-width: 34px; min-height: 34px; padding: 0; font-size: 18px; }\n.zones output { min-width: 2.2ch; text-align: right; font: 500 15px/1 var(--sans); font-variant-numeric: tabular-nums; color: var(--ink); }\n.hrs { display: flex; align-items: center; gap: 8px; margin-top: 10px; font: 400 14px var(--sans); color: var(--ink); }\n.say { margin: 10px 0 0; padding-top: 8px; border-top: 1px solid var(--rule); font: italic 400 15.5px/1.45 var(--serif); color: var(--ink-2); min-height: 3em; }\n.pg .sheet { fill: var(--paper); stroke: var(--ink); stroke-width: 1px; vector-effect: non-scaling-stroke; }\n.pg .grid { fill: none; stroke: var(--rule); stroke-width: 0.6px; vector-effect: non-scaling-stroke; }\n.pg .zone { fill: var(--wash); fill-opacity: 0.72; stroke: var(--ink); stroke-width: 1px; vector-effect: non-scaling-stroke; }\n.pg .zone.alt { fill: var(--mark); }\n.pg .over { fill: none; stroke: var(--ink); stroke-width: 2px; stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; }\n.pg .hcol { fill: none; stroke: var(--ink-3); stroke-width: 1px; vector-effect: non-scaling-stroke; }\n.pg text { fill: var(--ink); font-family: var(--sans); }\n.pg .lab { font-weight: 500; }\n.pg .dim { fill: var(--ink-3); }\n.pg .it { font-family: var(--serif); font-style: italic; fill: var(--ink-3); }\n@media (max-width: 560px) {\n  .body { flex-direction: column; align-items: stretch; gap: 14px; }\n  .page { flex: none; width: 172px; align-self: center; }\n  .bar { gap: 8px; flex-wrap: nowrap; }\n  .seg button { padding: 9px 8px; font-size: 13px; }\n  .long, .zones .nm small { display: none; }\n  .short { display: inline; }\n  .zones li { padding: 2px 0; }\n  .say { min-height: 0; }\n}",
  "js": "(function () {\n  var root = document.querySelector('.techo');\n  var svg = root.querySelector('#pg'), list = root.querySelector('#zones'), say = root.querySelector('#say'), hours = root.querySelector('#hours');\n  var NS = 'http://www.w3.org/2000/svg';\n  var GRID = 3.7; // mm, the Techo's grid\n  // Page sizes in mm; margins, date line and quote line simplified.\n  var FORMATS = {\n    a6: { name: 'Original, A6', w: 105, h: 148 },\n    a5: { name: 'Cousin, A5', w: 148, h: 210 }\n  };\n  var ZONES = [\n    { id: 'todo', name: 'To-do', hint: 'ticked off by evening' },\n    { id: 'notes', name: 'Notes', hint: 'meetings, calls, lists' },\n    { id: 'journal', name: 'Journal', hint: 'a few lines on the day' },\n    { id: 'sketch', name: 'Sketch', hint: 'fineliner and watercolour' },\n    { id: 'stuck', name: 'Stuck in', hint: 'tickets, stamps, receipts' },\n    { id: 'tomorrow', name: 'Tomorrow', hint: 'what carries over' }\n  ];\n  // Each preset shares out the page's rows as fractions.\n  var PRESETS = {\n    work: { hours: true, todo: 0.24, notes: 0.32, journal: 0.2, tomorrow: 0.12 },\n    sketch: { hours: false, todo: 0.12, sketch: 0.52, journal: 0.26 },\n    travel: { hours: true, todo: 0.1, journal: 0.4, stuck: 0.36 }\n  };\n  var fmt = 'a6', preset = 'work', rows = {}, total = 0, cols = 0;\n\n  function geom() {\n    var f = FORMATS[fmt];\n    var top = f.h * 0.075, bottom = f.h * 0.07, side = f.w * 0.06;\n    cols = Math.floor((f.w - 2 * side) / GRID);\n    total = Math.floor((f.h - top - bottom) / GRID);\n    var gw = cols * GRID, gh = total * GRID;\n    return { f: f, x: (f.w - gw) / 2, y: top, gw: gw, gh: gh };\n  }\n\n  function applyPreset(p) {\n    geom();\n    var P = PRESETS[p];\n    ZONES.forEach(function (z) { rows[z.id] = Math.round((P[z.id] || 0) * total); });\n    hours.checked = P.hours;\n  }\n\n  function used() { return ZONES.reduce(function (s, z) { return s + rows[z.id]; }, 0); }\n\n  function el(name, attrs, text) {\n    var e = document.createElementNS(NS, name);\n    for (var k in attrs) e.setAttribute(k, attrs[k]);\n    if (text != null) e.textContent = text;\n    svg.appendChild(e);\n    return e;\n  }\n\n  function draw() {\n    var g = geom(), f = g.f, fs = f.w * 0.038;\n    while (svg.firstChild) svg.removeChild(svg.firstChild);\n    svg.setAttribute('viewBox', '0 0 ' + f.w + ' ' + f.h);\n    el('rect', { class: 'sheet', x: 0, y: 0, width: f.w, height: f.h });\n    var d = '';\n    for (var i = 0; i <= cols; i++) d += 'M' + (g.x + i * GRID) + ' ' + g.y + 'v' + g.gh;\n    for (var j = 0; j <= total; j++) d += 'M' + g.x + ' ' + (g.y + j * GRID) + 'h' + g.gw;\n    el('path', { class: 'grid', d: d });\n    var dt = el('text', { class: 'lab', x: g.x, y: g.y - fs * 0.55, 'font-size': fs * 1.15 }, 'Mon 28 ');\n    var mo = document.createElementNS(NS, 'tspan');\n    mo.setAttribute('class', 'dim');\n    mo.setAttribute('font-size', fs * 0.9);\n    mo.textContent = 'September';\n    dt.appendChild(mo);\n    var qy = g.y + g.gh + f.h * 0.035;\n    el('text', { class: 'it', x: g.x, y: qy + fs * 0.4, 'font-size': fs * 0.95 }, 'the day’s quote');\n\n    var hc = hours.checked ? 3 : 0, zx = g.x + hc * GRID, zw = g.gw - hc * GRID;\n    if (hc) {\n      el('rect', { class: 'hcol', x: g.x, y: g.y, width: hc * GRID, height: g.gh });\n      for (var h = 6; h <= 24; h += 3) {\n        var hy = g.y + ((h - 6) / 18) * g.gh;\n        el('text', { class: 'dim', x: g.x + GRID * 0.35, y: Math.min(hy + fs * 0.9, g.y + g.gh - fs * 0.25), 'font-size': fs * 0.78 }, String(h));\n      }\n    }\n    var y = 0, n = 0;\n    ZONES.forEach(function (z) {\n      var r = rows[z.id];\n      if (!r) return;\n      var y0 = Math.min(y, total), y1 = Math.min(y + r, total);\n      if (y1 > y0) {\n        el('rect', { class: 'zone' + (n % 2 ? ' alt' : ''), x: zx, y: g.y + y0 * GRID, width: zw, height: (y1 - y0) * GRID });\n        if (y1 - y0 >= 2) el('text', { class: 'lab', x: zx + GRID * 0.5, y: g.y + y0 * GRID + fs * 1.15, 'font-size': fs }, z.name + '  ' + r);\n      }\n      y += r;\n      n++;\n    });\n    var over = y - total, free = total - y;\n    if (over > 0) el('line', { class: 'over', x1: zx, x2: zx + zw, y1: g.y + g.gh, y2: g.y + g.gh });\n    else if (free >= 2) el('text', { class: 'it', x: zx + GRID * 0.5, y: g.y + y * GRID + fs * 1.2, 'font-size': fs }, 'free, ' + free + (free === 1 ? ' row' : ' rows'));\n\n    var parts = ZONES.filter(function (z) { return rows[z.id]; }).map(function (z) { return z.name + ' ' + rows[z.id] + ' rows'; });\n    svg.setAttribute('aria-label', f.name + ' daily page, ' + total + ' rows of ' + cols + ' squares' + (hc ? ', hours down the left' : '') + ': ' + (parts.join(', ') || 'nothing planned') + (over > 0 ? ', ' + over + ' rows over' : ', ' + free + ' free') + '.');\n    var head = f.name + ': ' + total + ' rows of ' + cols + ' squares. ';\n    if (over > 0) say.textContent = head + over + (over = 1 ? ' row' : ' rows') + ' over the page: take ' + (over = 1 ? 'it' : 'them') + ' from somewhere.';\n    else if (free === 0) say.textContent = head + 'Every row spoken for. Tight, but it fits.';\n    else say.textContent = head + used() + ' planned, ' + free + ' left for whatever the day brings.';\n    ZONES.forEach(function (z) { list.querySelector('#o-' + z.id).value = rows[z.id]; list.querySelector('#o-' + z.id).textContent = rows[z.id]; });\n  }\n\n  ZONES.forEach(function (z) {\n    var li = document.createElement('li');\n    li.innerHTML = '<span class=\"nm\">' + z.name + '<small>' + z.hint + '</small></span>' +\n      '<button type=\"button\" data-z=\"' + z.id + '\" data-d=\"-1\" aria-label=\"One row less for ' + z.name + '\">−</button>' +\n      '<output id=\"o-' + z.id + '\" aria-label=\"' + z.name + ', rows\">0</output>' +\n      '<button type=\"button\" data-z=\"' + z.id + '\" data-d=\"1\" aria-label=\"One row more for ' + z.name + '\">+</button>';\n    list.appendChild(li);\n  });\n\n  function pressed(sel, attr, v) {\n    root.querySelectorAll(sel).forEach(function (b) { b.setAttribute('aria-pressed', String(b.getAttribute(attr) === v)); });\n  }\n\n  list.addEventListener('click', function (e) {\n    var b = e.target.closest('button');\n    if (!b) return;\n    var id = b.getAttribute('data-z');\n    rows[id] = Math.max(0, Math.min(total, rows[id] + Number(b.getAttribute('data-d'))));\n    preset = null;\n    pressed('[data-p]', 'data-p', '');\n    draw();\n  });\n  root.querySelectorAll('[data-p]').forEach(function (b) {\n    b.addEventListener('click', function () {\n      preset = b.getAttribute('data-p');\n      pressed('[data-p]', 'data-p', preset);\n      applyPreset(preset);\n      draw();\n    });\n  });\n  root.querySelectorAll('[data-f]').forEach(function (b) {\n    b.addEventListener('click', function () {\n      var old = total;\n      fmt = b.getAttribute('data-f');\n      pressed('[data-f]', 'data-f', fmt);\n      geom();\n      if (preset) applyPreset(preset);\n      else ZONES.forEach(function (z) { rows[z.id] = Math.round(rows[z.id] * total / old); });\n      draw();\n    });\n  });\n  hours.addEventListener('change', draw);\n  document.addEventListener('know-tokens', draw);\n\n  applyPreset('work');\n  draw();\n})();"
}
```
