BUILD 010 CURRENT CHANGES
=========================
See README010.TXT for the current layout, memory map, ABI 1.5 and tests.
The material below is the supplied build-009 design history; where it
conflicts, README010.TXT and current source are authoritative.

HIVE - CHARACTER-CELL GUI FOR MICROBEE - LIVING SPEC                 build 009
===========================================================================
Work from: build 009.  Supersedes 001-008 (kept in build001/ .. build008/).
KERNAL IS UNCHANGED since build 006 (ABI 1.4, KVER_MIN 4): build 009 is all app side.

0. WHAT EXISTS
--------------
  KERNAL.ASM / KERNAL.INC   resident kernal, ABI 1.4: KERNAL.BIN 1999 bytes (limit 2048)
                            + KEXT.BIN 339 bytes (extension segment at 4920h, room to 4C00h)
  APPSTUB.INC               installs the kernal if none compatible is resident
  CALC.ASM                  calculator (client app)
  DEMO.ASM                  two-box demo (client app) = template for new apps
  HIVE.ASM                  THE DESKTOP (build 009): calculator + clock + saying + NOTEPAD
                            on one screen, Tab between them.  80x24 only.  Run  E:HIVE
  NOTEMOD.INC               the notepad module (build 009)
  KEYTEST.ASM/.COM          stand-alone raw key-code reporter (build 009), see N1
  CALCMOD.INC CLOCKMOD.INC SAYMOD.INC NOTEMOD.INC   the app modules HIVE.ASM compiles in
  CALC.ASM CLOCK.ASM        the same calculator / clock as standalone .COMs (thin
                            wrappers round the modules; kept for testing)
  WIDGETS.INC               menus + dialogs, an APP-SIDE library (build 005)
  WIDDEMO.ASM               menu bar / dropdowns / alert / confirm demo (client app)
  FOCDEMO.ASM               two-box FOCUS demo (client app): Tab, rings, per-box keys
  Binaries: HIVE CALC DEMO FOCDEMO WIDDEMO CLOCK .COM (80x24)  + the *64.COM twins (64x16)
            + KEYTEST.COM (no screen size, BDOS only)
  Run in VibeDrone: put the .COM in a FOLDER, then  E:HIVE  E:CALC  E:DEMO  E:FOCDEMO  E:WIDDEMO  E:CLOCK.
  Verified inside VibeDrone 101.16: build 001 (user report).  Builds 002-009
  are verified in the Z80 harness only (section 10).

N. NOTES FOR BUILD 009  (things to do / know)
--------------------------------------------
  N1  PLEASE RUN  E:KEYTEST  IN VIBEDRONE and send me the hex list for, in this order:
      Left Right Up Down | Shift+each arrow | Ctrl+each arrow | Backspace Delete Enter Tab Esc |
      Ctrl+A Ctrl+B Ctrl+E Ctrl+F Ctrl+Q | Shift+Z Shift+X | z x.   (Space x3 quits.)  That
      settles Left / Home / End and the quote key, and each fix is a one-line change in np_binds.
  N2  Try the notepad: Tab x3 (or View > Go to notepad), type, Enter, Backspace, Right, Z / X
      for Up / Down, fill a page and watch it scroll.  Tab or Esc leaves it.
  N3  Known limits (all by design, D33-D42): 47 columns and 48 lines, no wrap, no Left arrow
      until N1, capital Z / X not typeable until N1, no save, silent refusals.
  N4  The kernal image is byte-identical to build 008, so an installed kernal is reused.
  N5  Test count: notepad 49 checks + 120 fuzz sequences, desktop 40 checks, all earlier suites
      unchanged and passing.  Not run in VibeDrone yet (as for builds 002-008).

1. DECISIONS FROM outline.txt (unchanged)
-----------------------------------------
  Own design, not a GEOS port.  Character-cell, Z80, VibeDrone first.
  Keyboard first, mouse later.  Everything is an event; one central loop;
  controls have bounds rects; every action has a command ID; one focused
  control.  First apps: clock, calculator, notepad, saying of the day.

2. DECISIONS MADE IN BUILDS 002-008  (veto any)
-----------------------------------------------
  D1  Assembler is sjasmplus; raw .COM output via --raw; build.sh.
  D2  Kernal is RESIDENT at 4000h; apps call it ONLY via the jump table.
  D3  Every app embeds a kernal copy and installs it if none is resident.
  D4  Kernal is screen-mode independent (cols/rows passed to K_OPEN).
  D5  Apps never touch screen RAM, colour RAM or port 08h.
  D6  Hotkeys match case-insensitively; keymap letters are UPPER case.
  D7  Bump KVER_MIN whenever the kernal changes at all (the stub only
      replaces a resident kernal if MAJ differs or resident MIN is lower).
  D8  (build 003) HIVE OWNS THE MACHINE.  All PCG is ours; nothing in PCG
      is saved or restored (user decision).  The screen is still saved and
      restored by K_OPEN/K_CLOSE so a dev run returns cleanly to the Shell.
  D9  (build 003) BORDERS ARE COLOUR, NOT CHARACTERS.  A window/box is a
      filled rect of colour with a 1-cell colour ring and its title on the
      ring's top row.  No | + - frames.
  D10 (build 003) BOXES ARE HIDDEN AND SHOWN BY COLOUR ONLY.  Their text
      stays in the char plane.  The off/canvas colour is dark grey 88h, NOT
      black (see section 7 for why).
  D11 Working title is HIVE (the Project's name).
  D12 (build 004) BOXES ARE FIXED, NON-OVERLAPPING TILES for now (answers the
      first open question for M4).  Overlap needs a char backing store.
  D13 (build 004) FOCUS IS A RING COLOUR CHANGE.  One box (or none) is
      focused; its ring switches normal -> focus colour.  Colour bytes only,
      no character writes.  Only VISIBLE boxes can be focused.
  D14 (build 004) THE KERNAL OWNS THE FOCUS KEY.  Tab (kTabFwd, default 09h,
      verified working in VibeDrone) cycles focus over visible boxes in slot
      order, wrapping.  Forward only - no back-tab (user decision).  The key
      is consumed; with no visible box it is an ordinary key.  Every focus
      change queues one EV_FOCUS.
  D15 (build 004) KEY ROUTING: the focused box's keymap (K_BOXKEYMAP) is
      searched FIRST, then the global keymap (K_SETKEYMAP), so a box can
      override a global hotkey while it has focus.
  D17 (build 005) KERNAL IMAGE IS TWO SEGMENTS: KERNAL.BIN at 4000h (full: 52
      bytes spare) and KEXT.BIN at 4920h (new code goes here, up to 4C00h).
      Same header, same version; app_install copies both.  Built by one source
      (sjasmplus DEVICE + SAVEBIN), so labels are shared.
  D18 (build 005) MENUS AND DIALOGS ARE AN APP-SIDE LIBRARY (WIDGETS.INC), not
      resident: they only use the jump table (D2) and cost the kernal nothing.
      Each app that uses them carries ~1.7K of code.
  D19 (build 005) POPUPS ARE MODAL AND SAVE WHAT THEY COVER.  K_SAVERECT /
      K_RESTRECT hold ONE rect of up to 512 cells (POPBUF 4C00-4FFF).  While a
      popup runs kModal is set: no Tab handling, no keymaps, every key is EV_KEY.
      Popups cannot nest; a menu closes before the dialog its command opens.
  D20 (build 005) NO TEXT-INPUT DIALOG YET (needs a line editor and Backspace;
      Left arrow and Backspace may share 08h).  Comes with the notepad (M7).
  D21 (build 005) COMMAND IDs MUST BE NON-ZERO (0 = nothing chosen / cancelled).
  D22 (build 006) THE KERNAL OWNS THE RTC: K_GETTIME reads it, so apps never
      touch ports 04h/07h and a different clock chip later only changes the
      kernal.  Source of truth for the hardware: VibeDrone 101.17 source - a
      64-byte MC146818-style RTC, OUT 04h = register select, OUT 06h = write,
      IN 07h = read, follows the host clock, BCD or binary (reg B bit 2), 12 or
      24 h (reg B bit 1, PM = hour bit 7).  K_GETTIME decodes all of that and
      returns binary, 24 h; it never writes the RTC (setting the time is later).
  D23 (build 006) THE CLOCK POLLS EVERY 8TH K_EVENT (~6 Hz) AND REDRAWS ONLY
      WHAT CHANGED.  Digits are 3x5 pixel glyphs drawn as COLOUR cells (each
      pixel 2x1 cells, spaces with a lit/unlit background) - no PCG, no characters
      for the digits.  Only the date row uses characters, and only when the
      date, the 12/24 h mode or AM/PM changes.
  D24 (build 006) 12/24 H IS A DISPLAY CHOICE (T / View menu), independent of
      the RTC's own mode.  12 h mode drops the leading hour zero and shows AM/PM.
      If the RTC is absent or returns nonsense the clock shows "No real-time
      clock found" (no software fallback clock yet).
  D25 (build 007) MULTIPLE APPS ON SCREEN = MODULES IN ONE PROGRAM (option 2 of
      the design chat).  HIVE.COM compiles each app in as a sjasmplus MODULE with
      its own 256-byte RAM block, its own box (kernal slot) and its own keymap.
      Apps are fixed at build time; there is no loader.
  D26 (build 007) MODULE INTERFACE.  An app file is an .INC that expects its
      includer to define its position and VARS, and provides  <app>_init,
      <app>_tick (called after every K_EVENT, focused or not) and, if it takes
      commands, <app>_cmd (A = command).  Standalone .COM builds are thin
      wrappers round the same module, so the module code is tested both ways.
  D27 (build 007) FOCUS = "WHO GETS KEYS".  Tab (kernal) moves the focus ring to
      the next app; only the focused app's box keymap is searched before the global
      keymap, so only it sees its keys.  Everyone gets ticks regardless of focus.
  D28 (build 007) COMMANDS ARE ROUTED BY ID, NOT BY FOCUS, so a menu item reaches
      its app while another has focus.  Ranges: 0-39 calculator, 40-49 clock, 50-59
      saying, 60-69 notepad (build 009), E0-EF desktop, F0 quit (reserved SYSTEM command: CMD_QUIT in the
      calculator module is now F0).  Keep ranges disjoint: the kernal flashes the
      calculator's button whenever a command with the same ID fires.
  D29 (build 007) THE DESKTOP OWNS THE MENU BAR (File: About, Quit.  View: 12/24
      hour, Next saying; build 009 adds View > Go to notepad and the Edit menu) and a
      status row ("Focus: <app>").
      LAYOUT, as changed in build 008 (80x24):
          row 0        menu bar
          rows 1-13    calculator 27x13 at (1,1)      clock 51x11 at (29,1)
          rows 15-20   saying 27x6 at (1,15)          rows 13-22: FREE, 51x10 at
                                                      (29,13) = the NOTEPAD's slot
          row 23       status row (focus name + key help)
      Calculator and clock sit straight under the menu bar; the saying is smaller and
      under the calculator; the notepad (build 009) fills the area under the clock.
      NOTE_X / NOTE_Y in HIVE.ASM name it.
  D30 (build 007, resized in 008) SAYING OF THE DAY, first cut: 27x6 box, 8 placeholder
      sayings of two lines (23 chars max each), picked by
      day + 7*month from the RTC (first one if no RTC), N or Space = next.  The
      table is placeholder text of my own; the real data format is still open.
  D31 (build 007) HIVE.COM IS 80x24 ONLY: calculator (27) + clock (51) do not fit
      side by side in 64 columns and 13 + 11 rows do not fit in 16.
  --- build 009: the NOTEPAD (M7).  Veto any. ---
  D32 (build 009) THE NOTEPAD IS THE 4TH MODULE (slot 3) in the 51x10 slot under the clock at
      (29,13), magenta ring 5Fh (focus: light magenta DFh).  Commands 60-69.  Its RAM is
      3400-34FF (variables) + 3500-3DCF (text buffer).  Kernal untouched.
  D33 (build 009) TEXT MODEL: A FIXED GRID, 48 lines x 47 columns, every line space padded,
      NO WORD WRAP, vertical scroll only.  The text area is 47x8 cells inside the box (ring
      1 cell + 1 cell margin each side, like the saying box).  Chosen because it is small,
      fast and exactly testable; 2256 bytes.  A line that is full (47) refuses more typing.
      Cost of the model: no horizontal scroll, long lines must be broken by hand.
  D34 (build 009) TYPING INSERTS (rest of the line moves right).  Backspace deletes left and
      at column 0 JOINS with the line above if the result fits in 47; Delete deletes at the
      cursor and at / past the end of the text pulls the next line up if it fits; Enter splits
      at the cursor and pushes the lines below down, refused when the last line (47) is in use.
      Refusals are silent (no beep yet).
  D35 (build 009) THE CURSOR IS A COLOUR CELL (NP_CUR 2Fh, white on green), no characters
      written for it, drawn only while the notepad has focus.  (A black-on-white cursor was
      avoided: a foreground nibble of 0 renders green in VibeDrone, section 7.)
  D36 (build 009) WHILE THE NOTEPAD HAS FOCUS THE KERNAL IS MODAL (kModal=1).  The desktop sets
      it on EV_FOCUS and re-asserts it after every command (popups clear it).  Consequences:
      every key is a raw EV_KEY to the notepad, so F V E Q are TEXT there, the kernal does not
      handle Tab and the global keymap (Esc = quit) is not consulted.
  D37 (build 009) TAB OR ESC LEAVES THE NOTEPAD (focus -> slot 0, the calculator).  To quit or use
      a menu from inside the notepad: Esc, then Q / F / V / E.  View > Go to notepad (P) comes
      back.  Esc in the notepad does NOT quit the desktop.
  D38 (build 009) KEY BINDINGS ARE A DATA TABLE (np_binds in NOTEMOD.INC): key -> action.
      Verified in VibeDrone's keymap (101.17 source): Backspace = ArrowLeft = 08h, Enter 0Dh,
      Esc 1Bh, Tab 09h, Delete 7Fh, ArrowRight 0Ah, ArrowUp = 5Ah ('Z'), ArrowDown = 58h ('X').
      Bound now: 08 Backspace, 7F Delete, 0D Enter, 0A Right, 5A Up, 58 Down, 09/1B leave.
      TENTATIVE (never seen on a real key yet, harmless if they never arrive): 02 Left,
      06 Right (the known Shift+ArrowRight), 01 Home, 05 End, 11 Ctrl-Q = quote.
  D39 (build 009) CAPITAL Z AND X CANNOT BE TYPED DIRECTLY (they ARE the Up / Down arrows, D38).
      Ctrl-Q (11h) makes the next key literal, so Ctrl-Q Z types Z - but only once Ctrl+letter
      is confirmed to arrive as 01h-1Ah.  Lower-case z and x are normal.
  D40 (build 009) NO LEFT ARROW YET: ArrowLeft is Backspace (08h, D38), so plain Left cannot exist
      until KEYTEST shows what Shift+ArrowLeft (or Ctrl+arrows) send.  02h is bound to Left on a
      guess (06h = Shift+ArrowRight is ^F, so Shift+ArrowLeft may well be ^B = 02h).
  D41 (build 009) EDIT MENU: Edit > Clear notepad (C) asks "Clear the notepad?" (default No).
      Hotkey E opens the Edit menu from any other app.  View > Go to notepad (P).  Command IDs:
      60 NOTE_CLEAR, E4 DK_NOTE, E5 DK_OPENE.  Bar: File x=1, View x=8, Edit x=15.
  D42 (build 009) NO FILE SAVE / LOAD YET.  The text lives in RAM and is lost on quit.  That is
      M8 (file manager) work; it needs a file dialog and a text-input dialog (D20).
  D16 (build 004) K_BOXTITLE (proposed in chat) DROPPED: the title already
      sits on the ring's top row in ring colour, so hide/show covers it.

3. MEMORY MAP  (all inside 0000-7FFF, the CF bank-0 window)
-----------------------------------------------------------
  0000-00FF  CP/M page zero
  0100-2FFF  app code + data + embedded kernal image (ASSERTed)
  3000-3FEF  app RAM (VARS at 3000h)        stack top 3FF0h
  4000-47FF  KERNAL code + header + jump table   (1984 bytes used, 64 spare)
  4800-48FF  kernal RAM: register block 4800-4805, variables, DIRTY (4880),
             ROWTAB (48A0), BOXTAB (48E0, 4 slots x 8 bytes)
  4900-491F  BOXEXT (build 004): per slot ringNorm, ringFocus, keymap word, flags
  (app RAM detail, HIVE.COM: 3000 calc, 3100 desktop, 3200 clock, 3300 saying, 3400-34FF notepad
   variables, 3500-3DCF notepad text buffer, 3E00-3E4F widget variables, stack 3FF0 and down.
   Measured worst-case stack use in the tests: 32 bytes.  HIVE.COM is 9.6 KB: code ends ~265Ah.)
  5000-57FF  shadow chars      5800-5FFF  shadow colour
  6000-67FF  saved chars       6800-6FFF  saved colour (screen at K_OPEN)
  7000-7FFF  box backing pool: colours of hidden boxes (4K, bump allocated)
  Kernal header at 4000h: "GUIK", MAJ, MIN, word end-of-kernal.

4. KERNAL ABI 1.2   (KERNAL.INC)
--------------------------------
  Entry         Addr   In                          Out
  K_OPEN        4008   B=cols C=rows               saves screen, seeds shadow,
                                                   clears all kernal state
  K_CLOSE       400B   -                           restores the saved screen
  K_EVENT       400E   -                           A=type B=data (sec. 5)
  K_PUTC        4011   A=char, rX rY rCol          rX advanced
  K_PUTS        4014   HL=0-term string, rX rY rCol
  K_FILL        4017   rX rY rW rH rCh rCol
  K_COLRECT     401A   rX rY rW rH rCol            colour only
  K_FRAME       401D   rX rY rW rH rCol            1-cell colour RING (spaces
                                                   in rCol).  CHANGED in 1.1:
                                                   was an ASCII box in 1.0
  K_SETKEYMAP   4020   HL=keymap
  K_SETBTNS     4023   HL=button table B=orgX C=orgY   draws the buttons
  K_FLASHCMD    4026   A=command                   flashes that button
  K_VSYNC       4029   -                           waits for vertical blank
  K_FLUSH       402C   -                           pushes dirty rows now
  K_FRAMES      402F   -                           HL=frame counter
  K_GETSIZE     4032   -                           B=cols C=rows
  K_CANVAS      4035   A=colour                    whole screen := A, colour
                                                   only; kOffCol := A
  K_BOXDEF      4038   A=slot 0-3, rect rX..rH     CF=1 bad slot / pool full;
                                                   box registered as VISIBLE
  K_BOXHIDE     403B   A=slot                      colours saved to the pool,
                                                   cells painted kOffCol
  K_BOXSHOW     403E   A=slot                      colours restored
  --- added in ABI 1.2 (build 004), KVER_MIN = 2 ---
  K_BOXRING     4041   A=slot B=normal C=focus   CF=1 bad / undefined slot.  Sets
                                                   the box's two ring colours and
                                                   repaints its ring now if visible.
                                                   Call it while the box is VISIBLE.
  K_FOCUS       4044   A=slot or FFh (none)      CF=1 if the slot is not defined and
                                                   visible.  Same slot as now = ok,
                                                   no change, no event.
  K_FOCUSGET    4047   -                           A = focused slot, FFh = none
  K_BOXKEYMAP   404A   A=slot HL=keymap (0=none) CF=1 bad / undefined slot
  Focus rules: K_OPEN = none focused.  K_BOXHIDE of the focused box first hands
  focus to the next visible box (or none), repainting rings, THEN hides, so the
  box comes back unfocused with its normal ring.  K_BOXDEF on the focused slot
  drops focus first; a (re)defined box starts with no ring colours and no
  keymap.  K_BOXHIDE / K_BOXSHOW of an undefined slot is now a true no-op
  (before: SHOW set the visible flag).  K_BOXRING on a hidden box stores the
  colours but paints nothing (never draw into a hidden box).
  Tunable: kTabFwd 4834 (default 09h, 0 = off).
  --- added in ABI 1.4 (build 006), KVER_MIN = 4 ---
  K_GETTIME     4053   HL = 7-byte buffer  ->  sec, min, hour (0-23), weekday (1-7,
                       1 = Sunday), day, month, year (0-99), all binary.
                       CF=1 if the RTC is absent or any field is out of range.
                       Clobbers AF BC DE HL.  Reads reg B once, then 00 02 04 06 07 08 09.
  --- added in ABI 1.3 (build 005), KVER_MIN = 3 ---
  K_SAVERECT    404D   rect rX rY rW rH          CF=1 if empty, > 512 cells, or a rect
                                                   is already held.  Saves chars + colours.
  K_RESTRECT    4050   -                           puts the held rect back exactly and marks
                                                   its rows dirty; no-op if none held
  kModal        483D   tunable: nonzero = modal (see D19).  Cleared by K_OPEN.
  Register block (VOLATILE across any kernal call; a frame routine moves
  rX): rX 4800  rY 4801  rW 4802  rH 4803  rCh 4804  rCol 4805.
  Tunables after K_OPEN: kFlashCol 481A, kFlashFr 481B, kOffCol 4832.
  Clobbers: every entry may clobber AF BC DE HL and the register block.
  IX IY and the alternate set are not used by the kernal; K_EVENT calls
  BDOS, so do not rely on IX/IY across it.  Interrupts are off only for the
  colour phase of a flush and their previous state is restored (NMOS
  LD A,I bug guarded).
  Backing pool: each box takes rW*rH bytes; 4096 in total, reset by K_OPEN.
  Hiding or showing an undefined slot is a harmless no-op.

5. EVENTS
---------
  K_EVENT: wait for vertical blank -> flush dirty rows -> frame counter and
  button-flash timer -> poll keyboard (BDOS 6) -> map key.  One event per
  call (so at most 50 per second).
    EV_TICK 1  a frame passed                                  B=0
    EV_KEY  2  key with no keymap entry                        B=key code
    EV_CMD  3  command fired                                   B=command ID
  EV_FOCUS 4 focus moved (1.2)                               B=new slot, FFh=none
  A pending EV_FOCUS is delivered by the next K_EVENT before the keyboard is
  polled (the ring repaint has already been flushed by then).  A Tab keypress
  returns its EV_FOCUS in the same call.
  Command IDs are app defined.  A hotkey, a button, and later a menu item or
  mouse click all produce the same EV_CMD.  PROPOSAL (not enforced): IDs
  F0h-FFh reserved for system commands.  Reserved: EV_MOUSE, EV_TIMER.

6. DATA FORMATS
---------------
  Keymap: DB key,cmd ... DB 0                    letters UPPER case
  Button: DB x,y,w,cmd,colour : DW label         7 bytes, ends DB 0FFh;
          x,y relative to the K_SETBTNS origin; label centred.
          A button is a bounds rect + command ID (mouse hit-test ready).
  Colour byte = bg<<4 | fg.

7. COLOUR, CANVAS, BOXES
------------------------
  In VibeDrone a FOREGROUND nibble of 0 renders as palette 2 (green), never
  black, in both colour orders (VD_FG3_DIRECT / VD_FG3_SWAP map 0 -> 2).  So
  black-on-black cannot hide text.  A hide colour must have fg==bg and
  render the SAME in both orders.  Exactly these bytes qualify:
        22 green   55 magenta   77 light grey   88 dark grey
        AA light green   DD light magenta   FF white
  88h is the default canvas / hide colour (kOffCol).  Body background may
  still be black (bg nibble 0); only the OFF colour has the restriction.
  To get a true black desktop later the kernal would also blank the box's
  characters on hide (a char copy, still no redraw) - not built.
  A box = a rect whose text stays in the char plane.  HIDE saves the colour
  rect to the pool and paints kOffCol; SHOW copies it back.  Measured:
  a toggle writes 0 character bytes (only the box's rows of colour).
  RULE: never draw into a hidden box (K_PUTC/K_FILL write colour, so it
  would reappear).  Boxes may not overlap (their text shares one char
  plane); overlap would need a char backing store too (not built).
  Press feedback on a button is a colour-only change (5 frames).
  Focus move (80x24): 0 character bytes, one flush of the box's rows of colour
  (9 rows x 80 = 720 colour bytes for a 28x9 box).  Measured.
  Costs (80x24, 3.375 MHz): idle frame 0 bytes; a keypress 80 chars + 160
  colour bytes; first paint of the calculator 1040 chars + 1920 colour
  (the canvas fill); a box toggle 0 chars + box rows of colour.

8. LOOK
-------
  Flat canvas (88h), boxes with a solid colour ring, title text on the top
  ring row in the same colour, black body, coloured key faces.  Ring colours
  so far: blue 4Fh, red 1Fh.  No glyphs, so no PCG is needed for this look.

9. APPLICATION SKELETON  (DEMO.ASM)
-----------------------------------
        INCLUDE "KERNAL.INC"
        ORG 0x100
  start: ld sp,STACK / call app_install / clear app vars
         ld b,cols / ld c,rows / call K_OPEN / ld a,CANVAS / call K_CANVAS
         draw each box (K_FILL, K_FRAME, K_PUTS) / K_BOXDEF per box
         K_SETKEYMAP / K_SETBTNS
  main:  call K_EVENT -> EV_CMD / EV_KEY / EV_TICK -> loop
  quit:  call K_CLOSE / jp 0
         INCLUDE "APPSTUB.INC" ... APP_KERNAL_IMAGE   (last in file)

10. TESTING (covered / not covered)
-----------------------------------
  Build 009 adds: notetest.py (all 80x24): layout, focus and kModal, Tab / Esc leave, typing,
  F V E Q as text, every editing key, scrolling, the 47-column and 48-line limits, Edit and View
  menus and the clear confirm, idle / per-key cost, hardware hygiene, stack depth, and a
  120-sequence FUZZ (70-190 random keys each) compared with a Python model of the editor: text
  buffer, cursor, scroll position, the 8 visible rows and the single cursor cell must all match.
  hivetest.py was updated for four apps.  Harness: new DUMP=addr:len (hex dump of RAM) and
  BDOSOUT=1 (prints BDOS 2 / 9 output) options.  KEYTEST.COM verified in the harness with
  known keys (5A 08 1B 0A 7F ... printed correctly, three Spaces quit).
  NOT covered for 009: VibeDrone itself, Ctrl / Shift arrow codes (that is KEYTEST's job),
  real typing speed / key repeat, a very long session.
  Build 008 re-checks the new layout (39 checks): rows, no overlap, the notepad slot
  is empty canvas (build 009 now fills it).
  Build 007 adds: hivetest.py (38 checks, 80x24): all three apps on screen, no overlap,
  Tab cycles focus with exactly one light ring and the status row following it,
  calculator keys reach only the calculator, T only the clock when it has focus, N
  only the saying; clock ticks while the calculator is used; idle frames write
  nothing; menus work over the boxes and restore the screen exactly; a View menu
  command reaches the clock while the calculator has focus; Tab inside a menu is
  Right; Q / Esc / File>Quit from every focus.
  Build 006 adds: harness RTC (env RTC="HH:MM:SS DD/MM/YY WD", RTCB=reg B, RTCSPEED=n:
  the clock runs with the Z80 cycle count), TIMETEST.ASM (no RTC, BCD, binary, 12 h
  PM / midnight / noon) and clocktest.py (55 checks, both modes: digits decoded from
  the colour cells, RTC modes, 12/24 h toggle, ticking vs the RTC within the update
  lag, midnight + date rollover, idle frames write nothing and ticks write no
  characters, no-RTC message, menus, exact restore, hardware hygiene).
  Build 005 adds: widgettest.py (77 checks, both modes: dropdown contents and
  highlight, Esc/Enter/hotkeys/wrap, bar Left/Right, exact screen restore after
  every popup, dialog buttons / hotkeys / Tab / Space / Esc, app hotkeys inert
  while modal, focus ring intact, no port 08h / PCG / interrupt violations) and
  SAVEEDGE.ASM (empty, 520 cells refused, 512 accepted, second save refused,
  restore with nothing held).
  Build 004 adds: focustest.py (39 checks, both modes: rings exact, colour-only,
  Tab / wrap, key routing and override, hide/show of the focused box,
  no visible box, K_FOCUS from the app, single visible box) and FOCEDGE.ASM
  (bad / undefined slots, hidden-box ring not repainted, redefining focused slot).
  Harness: Z80 core (superzazu/z80) + stubbed BDOS fn 6, 6545 vblank on port
  0Ch, banked colour RAM on port 08h, prefilled screen and PCG.
  Covered: calculator vs exact-Decimal model (~20,000 random sequences over
  builds, 112 edge cases, both builds, 0 mismatches); kernal install rules
  (same / newer / older minor, older major, cold); two apps chained; screen
  restored byte for byte after canvas + boxes; interrupt state restored
  from both initial states; box hide/show colour-only, exact restore,
  independent per box, no text visible while hidden (rendered and viewed);
  box edge cases (bad slot, pool exhausted, pool exactly full, undefined
  slot).  render.py draws a harness screen with VibeDrone's palette.
  NOT covered: VibeDrone itself for builds 002-008, a real RTC chip, month-end rollover
  in the fake RTC, the update-in-progress flag (reg A bit 7; VibeDrone never sets it), real hardware, the real
  Shell/BIOS, the NMOS LD A,I race.

11. PCG  (all ours; map deferred)
---------------------------------
  Decision D8: HIVE owns all PCG; no save/restore.  No glyph is allocated
  yet (the coloured-box look needs none).  Glyphs will be wanted for
  scrollbar arrows, the mouse pointer, icons and checkboxes.
  Facts read from the VibeDrone 101.16 source (not yet exercised):
   - char code bit 7 selects PCG; glyph = low 7 bits; bit 7 = leftmost pixel
   - PCG RAM is at F800 when port 08h bit 6 = 0 (colour RAM when 1)
   - port 1Ch: bits 0-3 bank mapped at F800, bit 4 attribute RAM at F000,
     bit 7 extended mode (per-cell bank = attribute bits 0-2)
   - 8 banks x 2K backed (banks 8-15 read zero); outline.txt said 32K
   - in 80-col mode an UNDEFINED glyph in 20h-7Ah falls back to the 80-col
     font; "undefined" = all 16 rows 00h or FFh
   - the renderer special-cases char codes 8Dh 9Eh BBh BEh DEh F9h FAh C9h
     (and apparently 9Fh BFh DFh) when its boxOn flag is set (I read only
     part of that code).  Until verified, do not allocate bank-0 glyph
     indices 0Dh 1Eh 1Fh 3Bh 3Eh 3Fh 49h 5Eh 5Fh 79h 7Ah.
  If ext mode is on the app must also clear attribute RAM (port 1Ch bit 4)
  so stale bank bits do not redirect cells; this needs testing in
  VibeDrone before use.

12. OPEN QUESTIONS
------------------
  - Overlapping boxes (needs a char backing store) - fixed tiles until then (D12).
  - Multiple apps visible at once: boxes persist on the canvas after a .COM
    exits (static) or apps become resident modules?  Single-task .COM today.
  - Enter / Esc key codes in VibeDrone (see 12b).
  - Should the off colour be true black (blank chars on hide)?
  - PCG allocation map (when the first glyph is needed).
  - Saying-of-the-day data format.  Launcher owning the kernal install (D3).
  - Notepad: Left / Home / End keys (needs KEYTEST results, N1); how to type capital Z / X;
    horizontal scroll or word wrap instead of the fixed 47-column grid (D33); a beep or flash on
    a refused key; blinking cursor; load / save (M8).
  - Should Esc from the notepad also be able to open a menu directly (e.g. F10)?

12b. WIDGETS (build 005)  -  see the header of WIDGETS.INC for the full ABI
----------------------------------------------------------------------
  W_MENU, W_BARDRAW, W_MBAR, W_DLG, W_ALERT, W_CONFIRM.  Menu = DB n,iw then
  DB key,cmd:DW label per item.  Bar = DB n then DB x:DW title:DW menu.  Dialog =
  DB iw,nlines,nbtn,esc,default : DW title : DW line.. : DB key,result:DW label..
  Menu keys: Up/Down move (wrap), Enter chooses, Esc cancels, Left/Right (or
  Tab) switch menu in a bar, item hotkey (case-insensitive).  Dialog keys:
  Left/Right/Up/Down/Tab move between buttons, Enter or Space presses, Esc =
  escape result, button hotkey.  Item / button and hotkey and menu all give the same command.
  KEY CODES (from VibeDrone's keymap, reported by the user): ArrowLeft 08h,
  ArrowRight 0Ah, ArrowUp = ASCII 'Z' (5Ah), ArrowDown = ASCII 'X' (58h),
  Shift+ArrowRight 06h, Tab 09h.  So raw UPPER-case Z / X ARE Up / Down and
  cannot be menu or button hotkeys (lower-case z / x can).  Outside a popup an
  arrow is just the Z / X hotkey.  Enter 0Dh and Esc 1Bh still unverified.
  Override WK_* before INCLUDE.
  A menu or dialog that cannot be saved (rect > 512 cells, or already open)
  reports "cancelled" and draws nothing.

13. MILESTONES
--------------
  M1 spec (this file)   M2 screen primitives DONE   M3 event loop DONE
  M3b canvas + boxes DONE (build 003)
  M4 box manager / focus: focus DONE (build 004); z-order/move not needed (D12)
  M5 data-driven menus, dialogs: DONE (build 005; text-input dialog deferred, D20)
  M6 clock: DONE (build 006)  Desktop with 3 apps: DONE (build 007)
  M7 notepad: DONE first cut (build 009; in-memory only, D42)   (saying: DONE, first cut)
  M8 file manager (open / save the notepad's text)   M9 mouse
  NEXT (proposal): run KEYTEST in VibeDrone and report the codes (N1), then fix np_binds;
  then M8 or a text-input dialog (D20) built on the notepad's line editor.
  (calculator, an M7-era app, was built early.)
