gitoriaLog in with ident

tickets

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commita75e0279a75e0279mission 010 (code order) 3/4: let only where a variable is reassigned or re-bound in a loop body (456 lets → plain declarations; Hybriel refuses a plain declaration inside a loop on its 2nd pass). gate 249/0, connect 60/0, real-data reads identical, a 50-step write sequence (API + faces) identical to the old codemrea75e0279/lib/mdview.hl

5.5 KB

  1. // lib/mdview.hl — THE MARKDOWN READ VIEW FOR LLM READERS (ticket #6, mission 014; CONCEPT.md "Data
  2. // formats": a Markdown read view for LLM readers). The ticket GET endpoints answer
  3. // `Accept: text/markdown` with a compact Markdown document instead of JSON (lib/api-helpers.hl
  4. // `wantsMarkdown`, `markdownReply`); JSON stays the default and is unchanged. Statics only.
  5. //
  6. // one ticket → # <project> #<n>: <subject>
  7. // state · opened (by) · updated · formerly · URL
  8. // the summary as written (it IS Markdown)
  9. // ## History — one `### <seq> · <when> · <author> <what>` per event, its text below
  10. // a list → # Tickets (<filters>): <count>
  11. // one line per ticket: `- <project> #<n> [<state>] <subject> · updated <when> · <url>`
  12. // Texts are embedded as written (they are Markdown already), so a text's own headings sit
  13. // inside the document — readers should take the `### <seq> ·` lines as the event boundaries.
  14. import { publicUrl } from './users.hl'
  15. static NL = "
  16. "
  17. // one line: line breaks and runs of spaces become one space
  18. static oneLine = (s) => {
  19. if (s == null) { return '' }
  20. parts = []
  21. for (w of ('' + s).replaceAll(NL, ' ').split(' ')) { if (w != '') { parts.push(w) } }
  22. return parts.join(' ')
  23. }
  24. // `> ` in front of every line (a previous summary inside an edit event)
  25. static quoted = (s) => {
  26. out = []
  27. for (l of ('' + s).split(NL)) { out.push(l == '' ? '>' : '> ' + l) }
  28. return out.join(NL)
  29. }
  30. static ticketLine = (row) => {
  31. return '- ' + row.project + ' ' + row.ref + ' [' + row.state + '] ' + oneLine(row.subject) + ' · updated ' + row.updated + ' · ' + publicUrl + row.href + relationTail(row)
  32. }
  33. // ---- relations (ticket #4, mission 017) --------------------------------------------------------
  34. // one ticket's document gets "Parent:", "Children (k of n done):", "Blocked by:", "Blocks:"
  35. // blocks under its URL; a list line gets ` · parent … · children n, k done · blocked by … · blocks …`
  36. static refName = (r) => { return r.project + ' ' + r.ref }
  37. static refLine = (r) => { return '- ' + refName(r) + ' [' + r.state + '] ' + oneLine(r.subject) + ' · ' + publicUrl + r.href }
  38. static confirmedOf = (refs) => {
  39. let n = 0
  40. for (r of refs) { if (r.state == 'done') { n = n + 1 } }
  41. return n
  42. }
  43. static relationTail = (row) => {
  44. parts = []
  45. if (row.parent != null) { parts.push('parent ' + refName(row.parent)) }
  46. total = row.children.length
  47. if (total > 0) { parts.push('children ' + total + (row.allChildrenConfirmed ? ', all done' : ', ' + confirmedOf(row.children) + ' done')) }
  48. if (row.blockedBy.length > 0) {
  49. bs = []
  50. for (r of row.blockedBy) { bs.push(refName(r) + ' [' + r.state + ']') }
  51. parts.push('blocked by ' + bs.join(', '))
  52. }
  53. if (row.blocks.length > 0) {
  54. bs = []
  55. for (r of row.blocks) { bs.push(refName(r)) }
  56. parts.push('blocks ' + bs.join(', '))
  57. }
  58. return parts.length == 0 ? '' : ' · ' + parts.join(' · ')
  59. }
  60. static relationLines = (t) => {
  61. lines = []
  62. if (t.parent != null) {
  63. lines.push('')
  64. lines.push('Parent: ' + refName(t.parent) + ' [' + t.parent.state + '] ' + oneLine(t.parent.subject) + ' · ' + publicUrl + t.parent.href)
  65. }
  66. total = t.children.length
  67. if (total > 0) {
  68. lines.push('')
  69. lines.push('Children (' + (t.allChildrenConfirmed ? 'all ' + total + ' done' : confirmedOf(t.children) + ' of ' + total + ' done') + '):')
  70. for (r of t.children) { lines.push(refLine(r)) }
  71. }
  72. if (t.blockedBy.length > 0) {
  73. lines.push('')
  74. lines.push('Blocked by:')
  75. for (r of t.blockedBy) { lines.push(refLine(r)) }
  76. }
  77. if (t.blocks.length > 0) {
  78. lines.push('')
  79. lines.push('Blocks:')
  80. for (r of t.blocks) { lines.push(refLine(r)) }
  81. }
  82. return lines
  83. }
  84. // a list: { tickets } (rows, as the JSON answer) + a label of its filters ('' = all)
  85. static listDocument = (rows, filters) => {
  86. lines = []
  87. lines.push('# Tickets' + (filters != '' ? ' (' + filters + ')' : '') + ': ' + rows.length)
  88. lines.push('')
  89. for (r of rows) { lines.push(ticketLine(r)) }
  90. return lines.join(NL) + NL
  91. }
  92. static eventHeadline = (e) => {
  93. let what = e.label
  94. if (e.kind == 'state') { what = 'changed the state: ' + (e.from != '' ? e.from : '?') + ' → ' + e.to }
  95. if (e.kind == 'edit') {
  96. parts = []
  97. if (e.subjectChanged) { parts.push('subject') }
  98. if (e.summaryChanged) { parts.push('summary') }
  99. what = 'edited the ' + parts.join(' and ')
  100. }
  101. return '### ' + e.seq + ' · ' + e.when + ' · ' + oneLine(e.author) + ' ' + what
  102. }
  103. // one ticket: { ticket (row), events (rows, oldest first) } as the JSON answer
  104. static ticketDocument = (data) => {
  105. t = data.ticket
  106. let opener = ''
  107. for (e of data.events) { if (e.kind == 'created' && opener == '') { opener = oneLine(e.author) } }
  108. lines = []
  109. lines.push('# ' + t.project + ' ' + t.ref + ': ' + oneLine(t.subject))
  110. lines.push('')
  111. let meta = 'state: ' + t.state + ' · opened ' + t.created + (opener != '' ? ' by ' + opener : '') + ' · updated ' + t.updated
  112. if (t.hasOld) { meta = meta + ' · formerly ' + t.oldRef }
  113. lines.push(meta)
  114. lines.push(publicUrl + t.href)
  115. for (l of relationLines(t)) { lines.push(l) }
  116. lines.push('')
  117. lines.push(t.summary != '' ? t.summary : '(no summary)')
  118. lines.push('')
  119. lines.push('## History')
  120. for (e of data.events) {
  121. lines.push('')
  122. lines.push(eventHeadline(e))
  123. if (e.kind == 'edit') {
  124. if (e.subjectChanged) {
  125. lines.push('')
  126. lines.push('Subject before: ' + oneLine(e.oldSubject))
  127. }
  128. if (e.summaryChanged) {
  129. lines.push('')
  130. lines.push('Summary before:')
  131. lines.push('')
  132. lines.push(e.oldSummary != '' ? quoted(e.oldSummary) : '> (empty)')
  133. }
  134. }
  135. if (e.hasText) {
  136. lines.push('')
  137. lines.push(e.text)
  138. }
  139. }
  140. return lines.join(NL) + NL
  141. }

Branches

Latest commits

  • a75e0279mission 010 (code order) 3/4: let only where a variable is reassigned or re-bound in a loop body (456 lets → plain declarations; Hybriel refuses a plain declaration inside a loop on its 2nd pass). gate 249/0, connect 60/0, real-data reads identical, a 50-step write sequence (API + faces) identical to the old codemre
  • e9d5c618mission 010 (code order) 2/4: one lib/ file per topic — store.hl split into projects / tickets (+ relations) / events / tickets-helpers, util.hl shared helpers (env, storage dir, URLs, sorts, Vienna time), the function routes out of project.hl into lib/api.hl (thin; auth/filters/Accept in api-helpers.hl), invite + member-removal logic out of the faces/routes into invites.hl / tickets.hl; project.hl is the map. /login/callback gets req + the session store by reference. gate 249/0, connect 60/0, real-data reads identicalmre
  • 97e269b5mission 010 (code order) 1/4: .hl files out of the root — lib/ (store, users, connections, invites, migrate, markdown, mdview, import = ticketfile, util = localtime, jsoncheck, api-helpers = api), tools/import.hl, components/styles.hl; import paths only. gate 249/0, connect 60/0, real-data reads identicalmre
  • 38f9d10ftickets: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); gate 249/0, connect 60/0mre
  • d3db6139tickets: Hybriel master 190aa11d (fc838894 GC correctness, #126 closure scopes, #127); gate 249/0, connect 60/0mre
  • bce182e3tickets: Hybriel master 7eea0d32 (#126 memory, #48 lambda copies its argument); migrate.hl lambdas take &logmre
  • 4137be0fantcolony#40: mission references point to the moved missionsmre
  • 9bfba36aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • c7bd2645tickets: Hybriel master 73267707 (#122 fixed); compactNow workaround removed (#110 covered)mre
  • 2ab91ee9tickets: gate checks rows appear once (session sync); re-vendor to ff51cf46 stopped on hybriel#122, stays 837fe120mre
  • e01c2b1dtickets#24: installable app (manifest, service worker, offline list), own icon; gate waits for the hello's pongmre
  • 752fbb7fdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • 38bdd5e4deploy.sh: never send .git or .gitignore to Byrodinmre
  • f12fa1bcState of 2026-09-27, before the move to gitoriamre