{"id":220,"date":"2026-05-26T08:17:37","date_gmt":"2026-05-26T15:17:37","guid":{"rendered":"https:\/\/csholgate.com\/?page_id=220"},"modified":"2026-05-28T18:58:19","modified_gmt":"2026-05-29T01:58:19","slug":"1922-a-writing-app","status":"publish","type":"page","link":"https:\/\/csholgate.com\/index.php\/1922-a-writing-app\/","title":{"rendered":"19:22 \u2014 a writing app"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Introducing <a href=\"https:\/\/apps.apple.com\/us\/app\/19-22\/id6769099256?mt=12\"><strong>19:22<\/strong>,<\/a> a simple writing app that does not let you delete beyond the current word. It is designed to embrace mistakes and get to an, imperfect, first draft. Reminiscent of writing on a typewriter, the only way to interact is forward. You cannot cut, copy, or paste text. The cursor position moves only as the user types forward and is otherwise immovable. Try it below!<\/p>\n\n\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     19:22 \u2014 a writing app where you can only move forward.\n     Web demo and app \u00a9 2026 Collin S. Holgate. All rights reserved.\n     Get the Mac app: https:\/\/apps.apple.com\/us\/app\/19-22\/id6769099256?mt=12\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n\n<style>\n  .nb1922 {\n    --bg:        #f5f0e6;\n    --fg:        #1f1812;\n    --accent:    #b85c38;\n    --accent-fg: #f5f0e6;   \/* text on the accent fill == canvas color *\/\n    --header-bg: #ede6d8;\n    --header-fg: #1f1812;\n    --faded:     #968a78;\n\n    --mono: ui-monospace, \"SF Mono\", SFMono-Regular, Menlo, Consolas, monospace;\n\n    -webkit-text-size-adjust: 100%;\n  }\n  @media (prefers-color-scheme: dark) {\n    .nb1922 {\n      --bg:        #191919;\n      --fg:        #f2f2f2;\n      --accent:    #c9a664;\n      --accent-fg: #191919;\n      --header-bg: #333333;\n      --header-fg: #bbbbbb;\n      --faded:     #888888;\n    }\n  }\n\n  .nb1922 { display: flex; justify-content: center; }\n  .nb1922 *, .nb1922 *::before, .nb1922 *::after { box-sizing: border-box; }\n\n  .nb1922-card {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    width: min(350px, 100%);\n    aspect-ratio: 600 \/ 848;          \/* 1 : \u221a2, mirrors the app window *\/\n    max-height: 40vh;\n    background: var(--bg);\n    border-radius: 14px;\n    overflow: hidden;\n    box-shadow: 0 18px 50px rgba(0,0,0,.28);\n  }\n\n  .nb1922-header {\n    flex: 0 0 auto;\n    position: relative;\n    height: 30px;\n    margin: 10px;\n    border-radius: 10px;\n    background: var(--header-bg);\n    display: flex;\n    align-items: stretch;\n    justify-content: space-between;\n  }\n\n  .nb1922-pill {\n    display: flex;\n    align-items: center;\n    padding: 0 10px;\n    border: 1px solid var(--bg);\n    border-radius: 10px;\n    font: bold 11px\/1 var(--mono);\n    white-space: nowrap;\n  }\n  .nb1922-name { color: var(--fg); }\n\n  .nb1922-export {\n    appearance: none;\n    -webkit-appearance: none;\n    margin: 0;\n    background: var(--accent);\n    color: var(--accent-fg);\n    cursor: pointer;\n  }\n\n  .nb1922-count {\n    position: absolute;\n    inset: 0;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    color: var(--header-fg);\n    font: 11px\/1 var(--mono);\n    pointer-events: none;\n  }\n\n  .nb1922-input {\n    flex: 1 1 auto;\n    width: 100%;\n    \/* wide windows pad the sides to center an 80-char column; narrow windows\n       fall back to a 56px side inset and let the column shrink. *\/\n    padding: 28px max(56px, calc((100% - 80ch) \/ 2));\n    border: 0;\n    outline: 0;\n    resize: none;\n    background: transparent;\n    color: var(--fg);\n    caret-color: var(--accent);\n    font: 14px\/1.4 var(--mono);\n    overflow-y: auto;\n    overflow-wrap: break-word;\n    white-space: pre-wrap;\n  }\n  .nb1922-input::placeholder { color: var(--faded); opacity: 1; }\n  .nb1922-input::selection    { background: transparent; }   \/* selection is invisible *\/\n\n  .nb1922-toast {\n    position: absolute;\n    left: 50%;\n    bottom: 18px;\n    transform: translate(-50%, 8px);\n    padding: 7px 14px;\n    border-radius: 999px;\n    background: var(--accent);\n    color: var(--accent-fg);\n    font: bold 11px\/1 var(--mono);\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity .25s ease, transform .25s ease;\n  }\n  .nb1922-toast.is-shown { opacity: 1; transform: translate(-50%, 0); }\n<\/style>\n\n<div class=\"nb1922\">\n  <div class=\"nb1922-card\">\n    <div class=\"nb1922-header\">\n      <span class=\"nb1922-pill nb1922-name\">19:22<\/span>\n      <span class=\"nb1922-count\">0 words, 1 line<\/span>\n      <button class=\"nb1922-pill nb1922-export\" type=\"button\" title=\"Export lives in the Mac app\">Export<\/button>\n    <\/div>\n\n    <textarea class=\"nb1922-input\" spellcheck=\"false\" autocomplete=\"off\" autocapitalize=\"off\" autocorrect=\"off\" placeholder=\"What I have written, I have written.\"><\/textarea>\n\n    <div class=\"nb1922-toast\">Export lives in the Mac app<\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function () {\n  document.querySelectorAll(\".nb1922\").forEach(function (root) {\n    var ta     = root.querySelector(\".nb1922-input\");\n    var count  = root.querySelector(\".nb1922-count\");\n    var btn    = root.querySelector(\".nb1922-export\");\n    var toast  = root.querySelector(\".nb1922-toast\");\n\n    var isWhitespace = function (ch) { return \/\\s\/.test(ch); };\n\n    \/\/ The caret is always pinned to the end of the text.\n    function caretToEnd() {\n      var n = ta.value.length;\n      ta.setSelectionRange(n, n);\n    }\n\n    function scrollToCaret() { ta.scrollTop = ta.scrollHeight; }\n\n    function updateCounts() {\n      var t = ta.value;\n      var words = t.split(\/\\s+\/).filter(Boolean).length;\n      var lines = t.length === 0 ? 1 : t.split(\"\\n\").length;\n      var w = words === 1 ? \"1 word\"  : words + \" words\";\n      var l = lines === 1 ? \"1 line\"  : lines + \" lines\";\n      count.textContent = w + \", \" + l;\n    }\n\n    \/\/ Delete exactly one character, and only if it isnt whitespace. Because\n    \/\/ the caret is always at the end, this lets you fix the current word but\n    \/\/ never cross a space or newline.\n    function deleteOneChar() {\n      var t = ta.value;\n      if (t.length === 0) return;\n      if (isWhitespace(t.charAt(t.length - 1))) return;\n      ta.value = t.slice(0, -1);\n      caretToEnd();\n      updateCounts();\n      scrollToCaret();\n    }\n\n    \/\/ All content mutation flows through beforeinput: allow plain typing and\n    \/\/ line breaks, reduce every delete gesture to a single guarded backspace,\n    \/\/ and reject paste \/ drop \/ formatting.\n    ta.addEventListener(\"beforeinput\", function (e) {\n      var type = e.inputType || \"\";\n      if (type.indexOf(\"delete\") === 0) {\n        e.preventDefault();\n        deleteOneChar();\n        return;\n      }\n      if (type.indexOf(\"insert\") === 0) {\n        if (type === \"insertFromPaste\" ||\n            type === \"insertFromPasteAsQuotation\" ||\n            type === \"insertFromDrop\" ||\n            type === \"insertFromYank\") {\n          e.preventDefault();\n        }\n        return; \/\/ insertText \/ insertLineBreak \/ insertParagraph \/ composition\n      }\n      e.preventDefault(); \/\/ formatting and everything else\n    });\n\n    ta.addEventListener(\"input\", function () {\n      caretToEnd();\n      updateCounts();\n      scrollToCaret();\n    });\n\n    \/\/ Block caret navigation and editing shortcuts.\n    var navKeys = [\"ArrowLeft\",\"ArrowRight\",\"ArrowUp\",\"ArrowDown\",\n                   \"Home\",\"End\",\"PageUp\",\"PageDown\"];\n    ta.addEventListener(\"keydown\", function (e) {\n      if (navKeys.indexOf(e.key) !== -1) {\n        e.preventDefault();\n        caretToEnd();\n        return;\n      }\n      if (e.metaKey || e.ctrlKey) {\n        var k = (e.key || \"\").toLowerCase();\n        if ([\"a\",\"c\",\"x\",\"v\",\"z\",\"y\"].indexOf(k) !== -1) {\n          e.preventDefault(); \/\/ select-all, copy, cut, paste, undo, redo\n        }\n      }\n    });\n\n    \/\/ Clicks focus the field but snap the caret to the end; no drag-selecting.\n    ta.addEventListener(\"mousedown\", function (e) {\n      e.preventDefault();\n      ta.focus();\n      caretToEnd();\n    });\n    ta.addEventListener(\"select\", caretToEnd);\n\n    \/\/ Belt-and-suspenders against clipboard \/ drag \/ context menu.\n    [\"copy\",\"cut\",\"paste\",\"dragstart\",\"dragover\",\"drop\",\"contextmenu\"]\n      .forEach(function (ev) {\n        ta.addEventListener(ev, function (e) { e.preventDefault(); });\n      });\n\n    \/\/ Export isnt part of the web demo \u2014 just acknowledge the tap.\n    var toastTimer = null;\n    btn.addEventListener(\"click\", function () {\n      toast.classList.add(\"is-shown\");\n      clearTimeout(toastTimer);\n      toastTimer = setTimeout(function () {\n        toast.classList.remove(\"is-shown\");\n      }, 1600);\n      ta.focus();\n      caretToEnd();\n    });\n\n    updateCounts();\n  });\n})();\n<\/script>\n\n\n\n<style data-wp-block-html=\"css\">\ninfo {\n  color: #999999;\n  font-style: italic;\n  font-family: Garamond, serif;\n  max-width: 600px;        \n  display: block;          \n  margin: 0 auto;  \n}\n<\/style>\n\n<info>Demo version produced by Claude Code, ported from Swift to HTML\/CSS\/JavaScript. This demo is not fully featured\u2014it does not contain markdown highlighting features, for example, and cannot export.<\/info>\n\n\n\n<p class=\"wp-block-paragraph\">The little distractions found in other writing apps are purposefully omitted. There are minimal user-configurable settings\u2014only increasing or decreasing the font size. Light and dark themes toggle automatically based on the system setting. But there is no settings page; no theme options or fonts to choose from; and there is no grammar or spell check to distract from the writing experience. Markdown support and theming is native.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The app autosaves on every character written. Because there is no copy or paste, the only way to get writing out of the app is by exporting. Markdown and plain text (stripping any user-written markdown) are currently supported, with a robust .docx export planned for the future. On a successful export, 19:22 is wiped clean and ready for another session.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">19:22 is a place for intentionally sloppy work. It is a place to make mistakes, knowing you&#8217;ll fix them later. It is an oven to proof ideas and to blunder through half-baked ones. It is an app written for me, someone who struggles with self-editing during first drafts in Word or LaTeX documents; I hope it can be useful to others, bringing a feeling of calm to their writing process. And if it doesn&#8217;t resonate with you, I hope you at least get a laugh at the ridiculousness. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I welcome feedback\u2014please reach out with any thoughts or feature requests you may have!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/apps.apple.com\/us\/app\/19-22\/id6769099256?mt=12\">It is available for Mac\u00ae and can be found on the Mac App Store.<\/a><\/p>\n\n\n\n<div style=\"height:205px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Introducing 19:22, a simple writing app that does not let you delete beyond the current word. It is designed to embrace mistakes and get to an, imperfect, first draft. Reminiscent of writing on a typewriter, the only way to interact is forward. You cannot cut, copy, or paste text. The cursor position moves only as [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":229,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-220","page","type-page","status-publish","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/pages\/220","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/comments?post=220"}],"version-history":[{"count":17,"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/pages\/220\/revisions"}],"predecessor-version":[{"id":309,"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/pages\/220\/revisions\/309"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/media\/229"}],"wp:attachment":[{"href":"https:\/\/csholgate.com\/index.php\/wp-json\/wp\/v2\/media?parent=220"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}