eklavya 1.54.0 → 1.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eklavya",
3
3
  "displayName": "Eklavya",
4
- "version": "1.54.0",
4
+ "version": "1.54.1",
5
5
  "description": "Learn while your agent works. Turns coding-agent generation time into adaptive, Socratic learning grounded in the code being written.",
6
6
  "author": {
7
7
  "name": "Ajay Kumar"
@@ -74,6 +74,20 @@ export function logoRows(grid: readonly string[] = LOGO_GRID, palette: Record<st
74
74
  return out
75
75
  }
76
76
 
77
+ /** The grid as rows of boxes for a host whose font cannot tile glyphs: runs of one colour (or none) and their width in cells. */
78
+ export function boxRows(grid: readonly string[] = LOGO_GRID, palette: Record<string, string> = LOGO_PALETTE): { width: number; color?: string }[][] {
79
+ return grid.map(row => {
80
+ const runs: { width: number; color?: string }[] = []
81
+ for (const c of row) {
82
+ const color = c === '.' ? undefined : palette[c]
83
+ const last = runs[runs.length - 1]
84
+ if (last && last.color === color) last.width++
85
+ else runs.push({ width: 1, color })
86
+ }
87
+ return runs
88
+ })
89
+ }
90
+
77
91
  /** The name as the header sets it: spaced capitals, the nearest a terminal gets to a larger type size. */
78
92
  /** A call such as `tester.tap(find.byType(X))`, nested up to three deep, a lowerCamelCase name or a snake_case name. */
79
93
  const CODE_LIKE = /[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*\((?:[^()\n]|\((?:[^()\n]|\([^()\n]*\))*\))*\)|\b[a-z]{2,}(?:[A-Z][a-z0-9]+)+\b|\b[a-z0-9]+(?:_[a-z0-9]+)+\b/g
@@ -90,6 +104,17 @@ export function codify(text: string): string {
90
104
  .join('')
91
105
  }
92
106
 
107
+ /** Block Markdown that bold marks around it would break: a fence, heading, list, quote, or bold already in it. */
108
+ const NOT_BOLDABLE = /```|\*\*|^\s*(?:#|[-*+>]|\d+[.)])\s/m
109
+
110
+ /** Bolds each plain paragraph of a question, so a blank line never sits inside a bold span. */
111
+ export function boldParagraphs(text: string): string {
112
+ return text
113
+ .split(/\n{2,}/)
114
+ .map(p => (p.trim() && !NOT_BOLDABLE.test(p) ? `**${p.trim()}**` : p))
115
+ .join('\n\n')
116
+ }
117
+
93
118
  /** What a Button label can show of Markdown: it keeps the words and drops the code and bold marks. */
94
119
  export function plainLabel(text: string): string {
95
120
  return text.replace(/`|\*\*/g, '')
@@ -16,6 +16,8 @@ import {
16
16
  codify,
17
17
  gradingRequest,
18
18
  logoRows,
19
+ boxRows,
20
+ boldParagraphs,
19
21
  plainLabel,
20
22
  levelLine,
21
23
  parseVerdict,
@@ -349,20 +351,38 @@ export const register: Register = on => {
349
351
  const s: PanelState = await read($, quiz)
350
352
  const q = s.question
351
353
 
352
- // The mark is drawn in text, as Claude Code draws its own, so it shows in every terminal.
353
- const logo = (
354
- <Box flexDirection="column" flexShrink={0}>
355
- {logoRows().map((runs, y) => (
356
- <Box key={`logo-${y}`} flexDirection="row" flexShrink={0}>
357
- {runs.map((run, x) => (
358
- <Text key={`logo-${y}-${x}`} color={run.fg} backgroundColor={run.bg} wrap="truncate">
359
- {run.text}
360
- </Text>
361
- ))}
362
- </Box>
363
- ))}
364
- </Box>
365
- )
354
+ // Any surface but the terminal is the desktop app's: it sets the question bold, spaces the answers
355
+ // apart and draws the mark in boxes. The terminal keeps its text layout.
356
+ const app = e.surface !== 'terminal'
357
+ // The mark is drawn in text, as Claude Code draws its own, so it shows in every terminal. The
358
+ // desktop app sets text in a proportional font with its own line height, so glyph cells do not
359
+ // tile there: it gets the same grid as filled boxes, one per run of a colour. A cell is one unit
360
+ // wide and half a unit tall, which is square in the app (checked live there; the host's
361
+ // fractional-height support is not documented, so a failure shows as a stretched mark).
362
+ const logo =
363
+ !app ? (
364
+ <Box flexDirection="column" flexShrink={0}>
365
+ {logoRows().map((runs, y) => (
366
+ <Box key={`logo-${y}`} flexDirection="row" flexShrink={0}>
367
+ {runs.map((run, x) => (
368
+ <Text key={`logo-${y}-${x}`} color={run.fg} backgroundColor={run.bg} wrap="truncate">
369
+ {run.text}
370
+ </Text>
371
+ ))}
372
+ </Box>
373
+ ))}
374
+ </Box>
375
+ ) : (
376
+ <Box flexDirection="column" flexShrink={0}>
377
+ {boxRows().map((runs, y) => (
378
+ <Box key={`logo-${y}`} flexDirection="row" flexShrink={0} height={0.5}>
379
+ {runs.map((run, x) => (
380
+ <Box key={`logo-${y}-${x}`} width={run.width} height={0.5} flexShrink={0} backgroundColor={run.color} />
381
+ ))}
382
+ </Box>
383
+ ))}
384
+ </Box>
385
+ )
366
386
  // A list tile: the mark on the left; the name over the topic beside it.
367
387
  const head = (subtitle?: string) => (
368
388
  <Box flexDirection="row" alignItems="center" gap={2}>
@@ -419,7 +439,7 @@ export const register: Register = on => {
419
439
  )
420
440
  }
421
441
 
422
- const stem = <Markdown text={codify(q.stem)} />
442
+ const stem = <Markdown text={app ? boldParagraphs(codify(q.stem)) : codify(q.stem)} />
423
443
 
424
444
  if (s.step === 'feedback' || s.step === 'skipped') {
425
445
  const r = s.result
@@ -469,7 +489,7 @@ export const register: Register = on => {
469
489
 
470
490
  return frame(
471
491
  stem,
472
- <Box flexDirection="column">
492
+ <Box flexDirection="column" gap={app ? 1 : 0}>
473
493
  {options}
474
494
  {card(
475
495
  'other',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eklavya",
3
- "version": "1.54.0",
3
+ "version": "1.54.1",
4
4
  "description": "Learn while your agent works — local knowledge graph, spaced repetition, and quiz gating for agent-assisted development.",
5
5
  "license": "MIT",
6
6
  "repository": {