Too many Embedded Logs? PureScript might help (Ox64 BL808 SBC / Apache NuttX RTOS)

📝 3 Mar 2024

Parsing Apache NuttX RTOS Logs with PureScript

Try the Online Demo

Watch the Demo on YouTube

Over the Lunar New Year holidays, we were porting QuickJS to Ox64 BL808 SBC. And we hit a Baffling Crash Dump on Apache NuttX RTOS

QuickJS crashes on Apache NuttX RTOS

Which made us ponder…

Parsing Apache NuttX RTOS Logs with PureScript (Overall Flow)

In this article, we create a NuttX Log Parser that will…

And we’ll do this in PureScript, the Functional Programming Language that compiles to JavaScript.

(We’ll see why in a moment)

Parsing Apache NuttX RTOS Logs with PureScript

Watch the Demo on YouTube

§1 Demo Walkthrough

To see our NuttX Log Parser in action, we run the NuttX Emulator in a Web Browser. (Pic above)

Inside the NuttX Emulator is the exact same NuttX App (QuickJS) that crashed over the holidays…

  1. Head over to this link…

    Emulator for Ox64 BL808 SBC

  2. Apache NuttX RTOS boots on the Ox64 Emulator…

    And starts our NuttX App: QuickJS

    Our NuttX App crashes with a RISC-V Exception

    NuttX App crashes with a RISC-V Exception

  3. The Terminal Output at left shows pages and pages of logs.

    (As seen by NuttX Devs today)

    But something helpful appears at the right…

  4. The NuttX Log Parser shows the RISC-V Exception Info

    Followed by the Explanation of the Exception

    RISC-V Exception Info and Explanation

    And the Stack Dump

    Stack Dump

  5. The NuttX Addresses are clickable.

    Clicking an address brings us to the NuttX Disassembly

    Which links to the NuttX Source Code. (Pic below)

    (Watch the Demo on YouTube)

NuttX Disassembly

What just happened?

Our NuttX App crashed on NuttX RTOS, producing tons of logs.

But thanks to the NuttX Log Parser, we extracted and interpreted the interesting bits: Exception Info, Exception Explanation and Stack Dump.

(With hyperlinks to NuttX Disassembly and Source Code)

How did we make it happen? We start with the smarty bits…

Explain the RISC-V Exception

§2 Explain the RISC-V Exception

How did we explain the RISC-V Exception?

“We hit a Load Page Fault. Our code at Code Address 8000a0e4 tried to access the Data Address 880203b88, which is Invalid”

That’s our message that explains the RISC-V Exception

In PureScript: This is how we compose the helpful message: Main.purs

-- Explain the RISC-V Exception with mcause 13
-- `<>` will concat 2 strings

explainException 13 epc mtval =
  "We hit a Load Page Fault."
  <> " Our code at Code Address " <> epc
  <> " tried to access the Data Address " <> mtval
  <> ", which is Invalid."

Hello Marvin the Martian?

Yeah we’ll meet some alien symbols in PureScript.

<>(Diamond Operator) will concatenate 2 strings.

We explain the other RISC-V Exceptions the same way: Main.purs

-- TODO: Explain the RISC-V Exception with mcause 12
-- `<>` will concat 2 strings
-- "🎵 I never promised you a rose garden"

explainException 12 epc mtval =
  "Instruction Page Fault at " <> epc <> ", " <> mtval

-- TODO: Explain the Other RISC-V Exceptions,
-- that are not matched with the above.
-- `show` converts a Number to a String

explainException mcause epc mtval =
  "Unknown Exception: mcause=" <> show mcause <> ", epc=" <> epc <> ", mtval=" <> mtval

Which looks like a tidy bunch of Explain Rules. (Similar to Prolog!)

This thing about PureScript looks totally alien: Main.purs

-- Declare the Function Type.
-- We can actually erase it, VSCode PureScript Extension will helpfully suggest it for us.

explainException ::
  Int        -- MCAUSE: Cause of Exception
  -> String  -- EPC: Exception Program Counter
  -> String  -- MTVAL: Exception Value
  -> String  -- Returns the Exception Explanation

But it works like a Function Declaration in C.

(VSCode will generate the declaration)

How will we call this from JavaScript?

Inside our Web Browser JavaScript, this is how we call PureScript: index.html

// In JavaScript: Import our PureScript Function
import { explainException } from './output/Main/index.js';

// Call PureScript via a Curried Function.
// Returns "Code Address 8000a0e4 failed to access Data Address 880203b88"
result = explainException(13)("8000a0e4")("880203b88");

// Instead of the normal non-spicy Uncurried Way:
// explainException(13, "8000a0e4", "880203b88")

Our JavaScript will call PureScript the (yummy) Curried Way, because PureScript is a Functional Language.

(Try PureScript Online)

PureScript looks like a neat way to express our NuttX Troubleshooting Skills as high-level rules

Why PureScript? Could’ve done all this in JavaScript…

PureScript looks like a neat way to express our NuttX Troubleshooting Skills as high-level rules…

Without getting stuck with the low-level procedural plumbing of JavaScript.

Let’s do a bit more PureScript…

Parse the RISC-V Exception

§3 Parse the RISC-V Exception

How did we get the RISC-V Exception? MCAUSE, EPC, MTVAL?

We auto-extracted the RISC-V Exception from the NuttX Log…

  EXCEPTION: Load page fault.
  MCAUSE:    000000000000000d,
  EPC:       000000008000a0e4,
  MTVAL:     0000000880203b88

PureScript really shines for Parsing Text Strings. We walk through the steps: Main.purs

-- Declare our Function to Parse the RISC-V Exception
parseException :: Parser  -- We're creating a Parser...
  {                       -- That accepts a String and returns...
    exception  :: String  -- Exception: `Load page fault`
  , mcause     :: Int     -- MCAUSE: 13
  , epc        :: String  -- EPC: `8000a0e4`
  , mtval      :: String  -- MTVAL: `0000000880203b88`

(VSCode will generate the declaration)

We’re about to create a PureScript String Parser that will accept a printed RISC-V Exception and return the MCAUSE, EPC and MTVAL.

This is how we write our Parsing Function: Main.purs

-- To parse the line: `riscv_exception: EXCEPTION: Load page fault. MCAUSE: 000000000000000d, EPC: 000000008000a0e4, MTVAL: 0000000880203b88`
parseException = do

  -- Skip `riscv_exception: EXCEPTION: `
  void $
    string "riscv_exception:" -- Match the string `riscv_exception:`
    <* skipSpaces             -- Skip the following spaces
    <* string "EXCEPTION:"    -- Match the string `EXCEPTION:`
    <* skipSpaces             -- Skip the following spaces

As promised, please meet our alien symbols…

Which will skip the unnecessary prelude…

riscv_exception: EXCEPTION: 

Next comes the Exception Message, which we’ll capture via a Regular Expression (and an alien raygun)

  -- `exception` becomes `Load page fault`
  -- `<*` says when we should stop the Text Capture
  exception <- regex "[^.]+" 
    <* string "." 
    <* skipSpaces 

We do the same to capture MCAUSE (as a String)

  -- Skip `MCAUSE: `
  -- `void` means ignore the Text Captured
  -- `$ something something` is shortcut for `( something something )`
  -- `<*` is the Delimiter between Patterns
  void $ string "MCAUSE:" <* skipSpaces

  -- `mcauseStr` becomes `000000000000000d`
  -- We'll convert to integer later
  mcauseStr <- regex "[0-9a-f]+" <* string "," <* skipSpaces

Then we capture EPC and MTVAL (with the Zero Prefix)

  -- Skip `EPC: `
  -- `epcWithPrefix` becomes `000000008000a0e4`
  -- We'll strip the prefix `00000000` later
  void $ string "EPC:" <* skipSpaces
  epcWithPrefix <- regex "[0-9a-f]+" <* string "," <* skipSpaces

  -- Skip `MTVAL: `
  -- `mtvalWithPrefix` becomes `0000000880203b88`
  -- We might strip the zero prefix later
  void $ string "MTVAL:" <* skipSpaces
  mtvalWithPrefix <- regex "[0-9a-f]+"

Finally we return the parsed MCAUSE (as integer), EPC (without prefix), MTVAL (without prefix)

  -- Return the parsed content.
  -- `pure` because we're in a `do` block that allows (Side) Effects
  -- TODO: Return a ParseError instead of -1
    , mcause:
        -1 `fromMaybe` -- If `mcauseStr` is not a valid hex, return -1
          fromStringAs hexadecimal mcauseStr -- Else return the hex value of `mcauseStr`
    , epc:
        epcWithPrefix `fromMaybe` -- If `epcWithPrefix` does not have prefix `00000000`, return it
          stripPrefix (Pattern "00000000") epcWithPrefix -- Else strip prefix `00000000` from `epc`
    , mtval:
        mtvalWithPrefix `fromMaybe` -- If `mtvalWithPrefix` does not have prefix `00000000`, return it
          stripPrefix (Pattern "00000000") mtvalWithPrefix -- Else strip prefix `00000000` from `mtval`

(fromMaybe resolves an Optional Value)

(fromStringAs converts String to Integer)

(stripPrefix removes the String Prefix)

fromMaybe looks weird?

We tried to make our code “friendlier”…

a `fromMaybe` b

Is actually equivalent to the Bracket Bonanza…

(fromMaybe a b)

(Maybe we tried too hard)

Does it work with JavaScript?

Yep it does! This is how we parse a RISC-V Exception in JavaScript: index.html

// In JavaScript: Import our PureScript Parser
import { parseException } from './output/Main/index.js';
import * as StringParser_Parser from "./output/StringParser.Parser/index.js";

// We'll parse this RISC-V Exception
const exception = `riscv_exception: EXCEPTION: Load page fault. MCAUSE: 000000000000000d, EPC: 000000008000a0e4, MTVAL: 0000000880203b88`;

// Call PureScript to parse the RISC-V Exception
const result = StringParser_Parser

Which returns the JSON Result

  "value0": {
    "exception": "Load page fault",
    "mcause":    13,
    "epc":       "8000a0e4",
    "mtval":     "0000000880203b88"

And it works great with our RISC-V Exception Explainer!

// In JavaScript: Import our Exception Explainer from PureScript
import { explainException } from './output/Main/index.js';

// Fetch the Parsed RISC-V Exception from above
// TODO: If the parsing failed, then exception === undefined
const exception = result.value0;

// Explain the Parsed RISC-V Exception.
// Returns "We hit a Load Page Fault. Our code at Code Address 8000a0e4 tried to access the Data Address 0000000880203b88, which is Invalid."
const explain = explainException

Let’s talk about log passing (and tossing)…

Pass NuttX Logs to PureScript

§4 Pass NuttX Logs to PureScript

PureScript will parse our RISC-V Exceptions and explain them… How to pass our NuttX Logs to PureScript?

We’re running NuttX Emulator inside our Web Browser.

We intercept all logs emitted by the Emulator, with this JavaScript: term.js

// When NuttX Emulator prints something
// to the Terminal Output...
Term.prototype.write = function(ch) {

  // Send it to our NuttX Log Parser

Our JavaScript parses NuttX Logs like this: term.js

// Parse NuttX Logs with PureScript.
// Assume `ch` is a single character for Terminal Output.
// PureScript Parser is inited in `index.html`
function parseLog(ch) {

  // Omitted: Accumulate the characters into a line.
  // Ignore Newlines and Carriage Returns
  termbuf += ch;
  // Parse the RISC-V Exception
  // TODO: Check for exception.error === undefined
  const exception = StringParser_Parser

  // Explain the Exception
  const explain = explainException

Line by line, we pass the NuttX Logs to PureScript, to parse the RISC-V Exceptions and explain them.

Then we display everything…

  // Link the Exception to the Disassembly
  const epc   = disassemble(exception.epc);
  const mtval = disassemble(exception.mtval);
  const exception_str = [
    "Exception:" + exception.exception,
    "MCAUSE:"    + exception.mcause,
    "EPC:"       + epc,
    "MTVAL:"     + mtval,

  // Display the Exception
  const parser_output = document.getElementById("parser_output");
  parser_output.innerHTML +=

  // Display the Exception Explanation
  parser_output.innerHTML +=
    .split(exception.epc,   2).join(epc)     // Link EPC to Disassembly
    .split(exception.mtval, 2).join(mtval);  // Link MTVAL to Disassembly

(We’ll see disassemble later)

We do the same for the Stack Dump

  // Parse the Stack Dump and link to the Disassembly
  // TODO: Check for stackDump.error === undefined
  const stackDump = StringParser_Parser

  // Display the Stack Dump
  const str = [
    stackDump.addr + ":",
    disassemble(stackDump.v1), disassemble(stackDump.v2), disassemble(stackDump.v3), disassemble(stackDump.v4),
    disassemble(stackDump.v5), disassemble(stackDump.v6), disassemble(stackDump.v7), disassemble(stackDump.v8),
  parser_output.innerHTML +=

  // Reset the Line Buffer
  termbuf = "";

// Buffer the last line of the Terminal Output
let termbuf = "";

(parseStackDump comes from PureScript)

(NuttX Emulator imports PureScript like this)

Will this work for a Real NuttX Device?

NuttX on Ox64 BL808 SBC runs in a Web Browser with Web Serial API and Term.js.

We’ll intercept and parse the NuttX Logs in Term.js, the exact same way as above.

What’s this function: disassemble?

Instead of printing addresses plainly like 8000a0e4, we show Addresses as Hyperlinks

<a href="disassemble.html?addr=8000a0e4" target="_blank">

Which links to our page that displays the NuttX Disassembly for the address: term.js

// If `addr` is a valid address,
// wrap it with the Disassembly URL:
// <a href="disassemble.html?addr=8000a0e4" target="_blank">8000a0e4</a>
// Otherwise return `addr`
function disassemble(addr) {

  // If this is an Unknown Address:
  // Return it without hyperlink
  const id = identifyAddress(addr).value0;
  if (id === undefined) { return addr; }

  // Yep `addr` is a valid address.
  // Wrap it with the Disassembly URL
  const url = `disassemble.html?addr=${addr}`;
  return [
    `<a href="${url}" target="_blank">`,

But we do this only for Valid NuttX Addresses. (Otherwise we’ll hyperlink to hot garbage)

How will identifyAddress know if it’s a Valid NuttX Address? Coming right up…

Identify a NuttX Address

§5 Identify a NuttX Address

Given a NuttX Address like 8000a0e4: How will we know if it’s in NuttX Kernel or NuttX App? And whether it’s Code, Data, BSS or Heap?

Once Again: We get a little help from PureScript to match the Regex Patterns of Valid NuttX Addresses: Main.purs

-- Given an Address: Identify the
-- Origin (NuttX Kernel or App) and
-- Type (Code / Data / BSS / Heap)
identifyAddress addr

  -- `|` works like `if ... else if`
  -- "a `matches` b" is same as "(matches a b)"
  -- `Just` returns an OK Value. `Nothing` returns No Value.

  -- Address 502xxxxx comes from NuttX Kernel Code
  | "502....." `matches` addr =
    Just { origin: "nuttx", type: Code }

  -- Address 800xxxxx comes from NuttX App Code (QuickJS)
  | "800....." `matches` addr =
    Just { origin: "qjs",   type: Code }

  -- Otherwise it's an Unknown Address
  | otherwise = Nothing

(matches does Regex Matching)

(Addresses as Numbers instead of Strings)

How does it work?

The code above is called by our JavaScript to Identify NuttX Addresses: index.html

// In JavaScript: Call PureScript to Identify a NuttX Address.
import { identifyAddress } from './output/Main/index.js';

// For NuttX Kernel Address:
// Returns {value0: {origin: "nuttx", type: {}}
result = identifyAddress("502198ac");

// For NuttX App Address:
// Returns {value0: {origin: "qjs", type: {}}
result = identifyAddress("8000a0e4");

// Why is the `type` empty? That's because it's a
// JavaScript Object that needs extra inspection.
// This will return "Code" or "Data" or "BSS" or "Heap"...
addressType =;

// Unknown Address returns {}
result = identifyAddress("0000000800203b88");

// This will return "Nothing"
resultType =;

Tsk tsk we’re hard-coding Address Patterns?

Our Troubleshooting Rules are still evolving, we’re not sure how the NuttX Log Parser will be used in future.

That’s why we’ll have an Online PureScript Compiler that will allow the Troubleshooting Rules to be tweaked and tested easily across all NuttX Platforms.

NuttX Disassembly

NuttX Disassembly for 8000_702A

§6 Disassemble NuttX by Address

Given a NuttX Address like 8000a0e4: How shall we show the NuttX Disassembly?

We chunked up the NuttX Disassembly into many many small files (by NuttX Address)…

## NuttX App Dissassembly (QuickJS)
## Chunked into 101 small files
$ ls nuttx-tinyemu/docs/purescript/qjs-chunk

(See the Disassembly Chunks)

So 8000a0e4 will appear in the file qjs-8000b000.S

// NuttX Disassembly for 8000a0e4
  p = rt->atom_array[i];
    8000a0e4:  6380  ld  s0,0(a5)

Which gets hyperlinked in our NuttX Log Display whenever 8000a0e4 is shown…

<a href="disassemble.html?addr=8000a0e4" target="_blank">

What’s inside disassemble.html? (Pic above)

Given a NuttX Address like 8000a0e4


disassemble.html will…

  1. Fetch the Disassembly Chunk File: qjs-8000b000.S

  2. Search for address 8000a0e4 in the file

  3. Display 20 lines of NuttX Disassembly before and after the address

  4. Hyperlink to the NuttX Source Code

    <a href="" target="_blank">

(More about disassemble.html)

How do we chunk a NuttX Dissassembly?

We created a NuttX Disassembly Chunker that will…

We run the chunker like this…

## Dump the NuttX Disassembly
## for NuttX ELF qjs
riscv64-unknown-elf-objdump \
  -t -S --demangle --line-numbers --wide \
  $HOME/qjs \
  >$HOME/qjs.S \

## Chunk the NuttX Disassembly
## at $HOME/qjs.S into
## $HOME/qjs-chunk/qjs-80001000.S
## $HOME/qjs-chunk/qjs-80002000.S
## ...
mkdir -p $chunkpath/$chunkbase-chunk
rm -f $chunkpath/$chunkbase-chunk/*

## Run the NuttX Disassembly Chunker
## TODO: Edit the pathnames in
git clone
cd nuttx-disassembly-chunker
cargo run -- $chunkpath $chunkbase

(See the Disassembly Chunks)

So what exactly caused our NuttX Crash Dump over the holidays? And made us ponder our life choices?

Our NuttX Stack was Full! There was a clear sign that we missed: The Loopy Stack Dump means that the NuttX Stack was obviously full.

That’s why we need a Smarter Log Parser that will catch these common problems. And stop NuttX Devs from falling into traps!

Parsing Apache NuttX RTOS Logs with PureScript (Overall Flower)

§7 What’s Next

Troubleshooting NuttX Crash Dumps will become a little less painful… Thanks to our new NuttX Log Parser!

Many Thanks to my GitHub Sponsors (and the awesome NuttX Community) for supporting my work! This article wouldn’t have been possible without your support.

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…

NuttX Log Parser

§8 Appendix: NuttX Log Parser

If we wish to update the NuttX Log Parser in PureScript: This is how we download and build the code…

## Download the NuttX Log Parser in PureScript
git clone
cd nuttx-purescript-parser

## Edit our code in `src/Main.purs`
code .

## Build and Run the NuttX Log Parser
spago run

## Deploy to GitHub Pages in `docs` folder

spago run will compile our NuttX Log Parser and generate… will rewrite the JavaScript Imports. (See the next section)

Remember to install the PureScript IDE VSCode Extension. It will auto-generate the Function Types when we click on the Suggested Type…

VSCode will auto-generate the Function Types when we click on the Suggested Type

§9 Appendix: Rewrite the Imports

From the previous section, PureScript Compiler “spago run generates the JavaScript for our NuttX Log Parser at…

Which means we need to deploy the Other JavaScript Modules. But there’s a workaround…

Here’s how we rewrite the JavaScript (generated by PureScript Compiler), so it points the JavaScript Imports to

## Change:
##   import { ... } from './output/Main/index.js';
## To:
##   import { ... } from './index.js';

## Change:
##   import * as StringParser_Parser from "./output/StringParser.Parser/index.js";
## To:
##   import * as StringParser_Parser from "";

cat index.html \
  | sed 's/output\/Main\///' \
  | sed 's/.\/output\//https:\/\/\/output\//' \

## Change:
##   import * as Control_Alt from "../Control.Alt/index.js";
## To:
##   import * as Control_Alt from "";

cat output/Main/index.js \
  | sed 's/from \"../from \"https:\/\/\/output/' \

(index.html is our Test JavaScript)

This produces the Modified JavaScript: docs/index.js

Which gets published at nuttx-purescript-parser/index.js

(See the Modified JavaScript)

(Run the Modified JavaScript)

How is this JavaScript imported by NuttX Emulator?

NuttX Emulator imports the Modified JavaScript for NuttX Log Parser like this: index.html

<script type=module>
  // Import the NuttX Log Parser
  import { parseException, parseStackDump, explainException, identifyAddress }
    from '';
  import * as StringParser_Parser
    from "";

  // Allow other modules to call the PureScript Functions
  window.StringParser_Parser = StringParser_Parser;
  window.parseException   = parseException;
  window.parseStackDump   = parseStackDump;
  window.explainException = explainException;
  window.identifyAddress  = identifyAddress;

  // Call the PureScript Function
  const result2 = explainException(12)('000000008000ad8a')('000000008000ad8a')

§10 Appendix: BigInt in PureScript

Why are we passing addresses in Text instead of Numbers? Like "8000ad8a"

That’s because 0x8000ad8a is too big for PureScript Int, a signed 32-bit integer.

PureScript Int is meant to interoperate with JavaScript Integer, which is also 32-bit.

What about PureScript BigInt?

spago install bigints
npm install big-integer

If we use PureScript BigInt, then we need NPM big-integer.

But NPM big-integer won’t run inside a Web Browser with Plain Old JavaScript. That’s why we’re passing addresses as Strings instead of Numbers.

TODO: BigInt is already supported by Web Browsers. Do we really need NPM big-integer?

Online PureScript Compiler

Try the Online PureScript Compiler

§11 Appendix: Online PureScript Compiler

How will we allow the NuttX Troubleshooting Rules to be tweaked and tested easily across all NuttX Platforms?

The Online PureScript Compiler will let us modify and test the NuttX Troubleshooting Rules in a Web Browser (pic above)…

  1. Head over to our Online PureScript Compiler

    Which compiles our NuttX Log Parser (from PureScript to JavaScript)

  2. Our NuttX Log Parser runs in the Web Browser…

    We hit a Load Page Fault. Our code at Code Address 8000a0e4 tried to access the Data Address 0000000880203b88, which is Invalid.
    Instruction Page Fault at epc, mtval
    Unknown Exception: mcause=0, epc=epc, mtval=mtval
    (Just { origin: "nuttx", type: Code })
    (Just { origin: "qjs", type: Code })
    (runParser) Parsing content with 'parseException'
    Result: { epc: "8000ad8a", exception: "Instruction page fault", mcause: 12, mtval: "8000ad8a" }
    (runParser) Parsing content with 'parseStackDump'
    Result: { addr: "c02027e0", v1: "c0202010", v2: "00000000", v3: "00000001", v4: "00000000", v5: "00000000", v6: "00000000", v7: "8000ad8a", v8: "00000000" }
    (runParser) Parsing content with 'parseStackDump'
    Result: { addr: "c02027e0", v1: "c0202010", v2: "00000000", v3: "00000001", v4: "00000000", v5: "00000000", v6: "00000000", v7: "8000ad8a", v8: "00000000" }
  3. Try tweaking the rules for explainException

    explainException 13 epc mtval =
      "We hit a Load Page Fault."
      <> " Our code at Code Address " <> epc
      <> " tried to access the Data Address " <> mtval
      <> ", which is Invalid."

    And identifyAddress

    identifyAddress addr
      | "502....." `matches` addr = Just { origin: "nuttx", type: Code }
      | "800....." `matches` addr = Just { origin: "qjs",   type: Code }
      | otherwise = Nothing

    The changes will take effect immediately.

    (Watch the Demo on YouTube)

  4. Future Plans: We’ll copy the Generated JavaScript to NuttX Emulator via JavaScript Local Storage.

    So we can test our Modified NuttX Log Parser on the Actual NuttX Logs.

    The PureScript Compiler Web Service is super helpful for compiling our PureScript Code to JavaScript, inside our Web Browser.

    (See the Generated JavaScript)

Parsing Apache NuttX RTOS Logs with PureScript (Overall Flowest)

If we wish to run the Online PureScript Compiler locally on our computer…

## Download the Online PureScript Compiler
git clone
cd nuttx-trypurescript
cd client

## To Build and Test Locally:
## This produces `output` folder
## and `public/js/index.js`
## Test at
npm install
npm run serve:production

## To Deploy to GitHub Pages in `docs` folder:
rm -r ../docs
cp -r public ../docs

## To Test Locally the GitHub Pages in `docs` folder:
cargo install simple-http-server
simple-http-server .. &

The Test Code comes from our GitHub Gist.