1. What's mnm ?
mnm is not mail.
The mnm project is building a replacement for email: a client (this app), a server, and a simple protocol (TMTP) between them.
mnm has two major goals.
- To provide a far safer correspondence model, where you:
+ choose the organizations/sites that relay your correspondence
+ select which members of a site can correspond with you
+ always know from which site a message originated
+ can block anyone with whom you've made contact
+ may leave a site and never see traffic from it again
- To offer capabilities missing in traditional email, including:
+ forms/surveys whose results are collected into tables
+ data-driven charts via [a JS chart library TBD]
+ slide deck layouts
+ hashtags and private tags
+ hyperlinks to messages & other threads
+ message formatting & layout via Markdown (aka CommonMark)
+ many more features to foster efficiency, creativity, focus, and understanding
Further reading: Why TMTP?
1a. Encryption
mnm employs TLS (aka SSL) to secure connections between the app and each server it connects to. mnm does not offer end-to-end encryption (where apps en/decrypt messages before/after transmission).
The author encourages organizations that transmit highly sensitive information via electronic messaging to propose enhancements to the mnm software for their needs.
For individuals who desire end-to-end email encryption, the author suggests any of the secure, audited electronic mail offerings on the market.
2. Look & Feel
Warning! Confirmation dialogs for send, etc. are [not yet implemented]. Undo for edit, delete, etc. are [not yet implemented].
Panels. mnm divides its screen into three panels. The message panel is on the left, so its left margin (the most-seen region for left-to-right languages) is free from distractions, helping the reader/writer to concentrate. The accounts panel is on the right, so its frequent activity notifications are far from the normal focus of the user. The thread index panel is between them.
Colors. The color scheme is intended to reduce the glare of black-on-white designs, and foster concentration. The accounts panel is dark, to de-emphasize its constant updates. Received and sent messages appear on a background reminiscent of parchment, to imply a sense of permanence. Input fields have a white background for emphasis.
Controls.Feature menus are indicated by an icon without a border. | |
etc |
Hyperlinks typically open a browser tab or alter a view, but downloads a file, and copies markdown to the clipboard. They appear as an icon, or text with an icon, in blue. |
etc | Hyperlinks that pop up a viewer are prefixed with a triangle. |
ETC × | Tabs, which alter a panel or sort a list, have a title in all-caps and display a blue underscore on the current tab. |
Buttons, which change the state of the app, have an icon and show help text when the mouse hovers on them. | |
Input menus display ▽ when they contain multiple items. When open, an item may be removed by its × button. | |
To and Group input fields pop up a keyboard-navigable menu of aliases or groups matching the input text. | |
12-31 | Dates show the full date & time when the mouse hovers on them. |
Browser Tabs. When you have multiple mnm accounts, each one is presented in a separate browser tab. Tabs may be closed and re-opened without impacting the app's connection to the TMTP service, as the app runs separately from the browser.
An mnm app optimized for small screens is ... [not yet implemented].
3. Accounts
The right-hand panel lists your accounts on TMTP services. The account presented in the current browser tab has a menu which displays its configuration. Other accounts have a menu that summarizes recent activity therein.
An account's user ID (UID) and password are randomly generated by the service and stored on the client device. Both are equally unguessable. The UID is revealed to other service members only by sending or replying to an invite. The password is dedicated to one client device, and is never entered by hand.
To create an account, open the menu and fill in the form. It takes the Site Address of the TMTP service (e.g. +someplace.net:443), Your Name/Alias by which other service members know you, and the Account Title to display in the accounts panel (e.g. the name of the organization providing the service). The Site Address is prefixed by + or = to indicate whether the service's TLS certificate can be verified or not. Not all addresses contain a :number component.
A service may allow you to create more than one account. A service may require member-specific credentials (e.g. for Radius, Kerberos, or LDAP) [not yet implemented].
If you give a name/alias that is in use by another service member, the account will be created, but not usable until you change the alias. To do so, open the menu on the accounts panel, and use the Alias (taken) field.
If the mnm app cannot contact the service (e.g. because it is down, or the site address is incorrect), the account will be created, but not usable until it makes contact. To change the address, open the menu on the accounts panel, and use the Site field.
To change the Account Title, [not yet implemented].
3a. Aliases
Aliases are the names by which service members know each other. An alias conveyed verbally or by phone is used in an invite to make initial contact with another service member. Users may create additional aliases after joining a service, corresponding to different roles, etc.
To create an alias, open the menu on the accounts panel, and use the +Alias form. [not yet implemented on client]
Aliases cannot be transferred to a different account (and therefore cannot be removed), but may be deactivated and reactivated. [not yet implemented]
3b. Replicas
You can replicate your accounts on other devices running the mnm app. Each copy of an account is known as a replica, and has a unique, server-generated password. All replicas receive every message sent to the account. Replicas provide automatic backup of account data, and access to it from multiple devices.
Private content (e.g. message tags, message seen status, the last-seen notice, ohi-to contacts, pinned tabs, and account configuration) is kept in sync across your replicas. Synchronization updates are distributed every two minutes. Synchronization of message drafts and invite drafts is [not yet implemented]. Peer-to-peer synchronization between replicas residing on the same local network is [not yet implemented].
Replicas of an account are listed in the menu. The first entry represents the original instance of the account. The login status and history for each replica is [not yet implemented].
To create a replica, open the menu above the accounts panel on the destination device, and click the button. The menu will display the address and pin required to make a replica. On the origin device, open the menu and fill in the form with the Target Address and Target Pin you've obtained. Also provide a New replica name, and then click the button.
Creating a replica is easy between devices on the same local network. When one of the devices is non-local, the Target Address must give the public IP address of the destination device, and any firewalls between that device and the Internet must be configured to allow incoming traffic on the mnm app's HTTP port (typically 8123). After a replica is created, the devices can reside on different networks without special configuration.
If a replication cannot be completed immediately, the replica's status will indicate Pending if the account's service is unreachable, or Accepted if the destination device becomes unreachable, or Ready if the origin device crashed after the destination received all the data. To complete a replication, obtain and fill in the Target Address and Target Pin as above, then click the button in the entry for the incomplete replica. A Pending or Accepted replica can be completed on a different destination device.
To rename a replica, open the menu and ... [not yet implemented]
To deactivate a replica, open the menu and ... [not yet implemented]
4. Contacts
The menu groups features for managing contacts, i.e. other members of the service with whom you can correspond.
4a. Invitations
A service member makes initial contact with another member by sending an invite (a.k.a. ping). Invites include the sender's UID & alias, the recipient's alias, an optional group name (GID), and an optional short, plain-text message. The sender is not notified if the recipient's alias is deactivated.
Invites are also generated for any original or subsequent thread recipients with whom the others have not previously made contact. Such invites display the message via [Contact Name] and a response hyperlink to the thread in question.
Receipt of an invite adds the sender's UID & alias to the recipient's address book. The recipient may respond by sending an invite themselves, by starting a thread with the sender, or by joining the group if one is included. Issuing a response is optional.
Received and generated invites are logged in the menu under the Invites tab. Sent invites are logged under the Sent tab. Invites for which responses have been sent or received indicate the date of the response and its type:
response via invite | |
response via thread | |
this invite is a response or followup | |
group invitation accepted | |
recipient joined the group |
To view the thread & message that delivered an invite response, click the hyperlink, which adds a ⦒ Response tab above the message list. (For responses of other types, no hyperlink appears on the invite.)
To compose an invite, open the menu, select the Drafts tab, and enter an alias in the To field. To include a group in the invitation, click the hyperlink to reveal the Group field and enter a group name (see Groups). Click the button to create a draft invite. Add a Message to the draft if desired; its text is automatically saved. The Message is limited to 140 characters, but most emoji count for two characters. A draft's To and Group fields cannot be updated; to change them, delete the draft and create another.
To send an invite, click its button. If it cannot be sent immediately, it is queued, which is indicated by the icon on the draft.
To delete a draft unsent, click its button.
After receiving an invite or a thread started in response to a sent invite, the recipient may block further communication from the sender (see Blocking). The sender is not notified that the recipient blocked them. [not yet implemented]
4b. Groups
A group provides an alias to a set of service members. As currently designed, this feature is suitable for communities of friends or colleagues who trust each other, not for public discussions to which any service member can subscribe. One joins a group by accepting an invite from a current group participant. Existing participants receive a notification when someone joins, leaves, updates their group membership info, or issues an invite. A message delivered via a group does not list the participants who received it. Group membership lists are stored on the TMTP server.
Groups which the user has joined or created are listed in the menu under the Groups tab. Those created by the user are marked with Ⓐ. Received and sent invitations are logged in the menu under the Invites and Sent tabs.
To invite someone to join a group, compose an invite including the group name (see Invitations). If the group name is unknown and available, it will be created. On the Drafts tab, the Choose group field presents a menu of known groups. To copy a name from the menu into the Group field, press enter after selecting a menu item. Having created a draft, add a Message if desired and send the invite.
To join a group in response to an invite, open the menu, select the Invites tab, and click the button next to the group name. If the acceptance cannot be sent immediately, it is queued, which is indicated by the icon on the invite.
To list a group's membership, any current participant may... [not yet implemented]
To change one's group membership info, any current participant may... [not yet implemented on client]
To drop oneself or another group participant from the group, any current participant may... [not yet implemented on client]
4c. Ohis
A service member may designate contacts to be notified when dis/connecting from/to the service. This presence notification is referred to as an ohi. Service members automatically receive ohis from any contacts who have so designated them. Ohis do not carry a message.
The list of online contacts who have chosen to notify you appears in a floating panel with dark background at the bottom-right corner of the threads panel. Those contacts whom you also notify are flagged with ◎. To hide or show the floating panel, click the o/ icon next to the menu.
To add a contact to receive ohis from you, open the menu, select the Ohi To tab, use the To field to select an alias, press enter, then click the o/ button.
To drop a contact receiving ohis from you, open the menu, select the Ohi To tab, and click the button for that contact.
Ohis are not delivered to a recipient who has blocked the sender.
4d. Blocking Other Members
[Not yet implemented].
5. Attachable Files
Files attached to outgoing messages are added to the menu. A file may be safely removed from the menu after it's been attached, but leaving it there doesn't consume any extra storage space. Files may also be uploaded to this menu without attaching them.
To upload a file, open the menu, click the Choose File or Browse button and select a file, amend the upload name as necessary, and click the button. Uploading by drag-and-drop is [not yet implemented].
To copy a file attachment from the most-recently selected thread to the menu, open the 📎 menu and click the attachment's button. [not yet implemented]
The list of files may be sorted using the Date and Name tabs.
To download a file, click its hyperlink. (Note: the □ character is appended to download filenames which may be reserved by the OS.) To preview a file, click its filename hyperlink. Files that can be previewed display a icon.
To delete a file, click its button.
6. Blank Forms
Users may create forms/surveys, similar to those on web pages, to request specific data from their correspondents. Blank forms are shared as attachments. Filled-form results are returned as attachments, and automatically appended to Filled-Form Tables.
Blank forms are grouped into families. All forms in a family produce results containing the same set of fields (i.e. data type). Form names have the format family.revision. Each revision defines a variation of the form (e.g. for use with different audiences). A family with more than one revision must also have a family.spec object. A blank form spec defines the data type produced by the family, and is used to validate filled-form results.
A blank form spec may contain a data type specification, or a filled-form name (FFN) that points into an online repository of specifications. [In this release, any FFN lookup returns the contents of the file formspec in the app directory.] A data type specification is a JSON object with an array of field definitions. Each definition has name, status, and type members. An array definition also has an array member indicating the number of dimensions. Type object fields have a nested spec list. This example illustrates all the options:
{"spec":[ {"name":"nr", "status":"required", "type":"number" }, {"name":"so", "status":"optional", "type":"string" }, {"name":"bd", "status":"deprecated", "type":"bool" }, {"name":"or", "status":"required", "type":"object", "spec":[ {"name":"anr", "status":"required", "type":"number", "array":2}, {"name":"aso", "status":"optional", "type":"string", "array":1}] } ]}
This version of mnm embeds the Vue Form Generator to render blank forms and produce results from user input. See the VFG docs covering form definition objects. This example definition conforms to the above specification:
{"fields":[ {"label":"SO", "model":"so", "type":"input", "inputType":"text"}, {"label":"NR", "model":"nr", "type":"input", "inputType":"number"}, {"label":"ASR", "model":"or.asr", "type":"checklist", "listBox":true, "values":["a","b"]}, {"label":"ANO", "model":"or.ano", "type":"checklist", "listBox":true, "values":[{"name":"12", "value":[1,2]}, {"name":"22", "value":[2,2]}] } ]}
Blank form field names, given by name variables in a spec list, and model variables in a form definition, may not begin with $ (dollar sign), as mnm uses that symbol internally.
To create a blank form, open the ⍰ menu, enter a family name and optional revision name (separated by ".") in the New Type field, and click the button. For a blank form spec, use the revision name .spec.
To edit a new or existing form, open the ⍰ menu and click its hyperlink to open the form editor. Next click the {...} hyperlink to switch to code view, and begin editing. Click {...} again to preview changes. Edits are automatically saved, except when the code is not valid JSON. Closing the editor or switching to a different form drops the latest invalid changes, if any.
To copy an existing form to a new revision, open the form editor for the source form and enter a revision name (without the family name) in the form at the bottom of the editor panel.
To copy a blank form attachment from the most-recently selected thread to the ⍰ menu, open the 📎 menu and click the attachment's button. [not yet implemented]
The list of forms may be sorted using the Date and Name tabs. Families remain grouped when sorted by date.
To delete a form, click its button.
7. Thread Lists & Search
The middle panel presents the service's logo (if available) [not yet implemented] and the account title, followed by a listing from the thread index.
The built-in listing options are:ALL | all threads. |
UNREAD | threads with any messages that have not been opened. |
#TODO | threads with any messages tagged as such. |
To add a listing of threads containing a specific tag, open the menu and click the tag's hyperlink (see also Tags).
The search box adds a listing of threads that match the entered query. To either require or disallow certain words in every thread of the result set, prefix any words in the query with + or -. To require a phrase in every thread of the result set, append or prepend = to the entire query.
For documentation of the menu, see Filled-Form Tables.
To sort the thread list by the date of the initial message in each thread, click ... [not yet implemented]. To sort the thread list by the date of the most recent message in each thread, click ... [not yet implemented].
Within the thread list, the two right-hand columns give the date of the initial message and either its sender, or its recipients (in italics) when this account is the initial sender.
Threads with unopened messages present the date & sender of the most recent message in boldface text.
To display a thread in the left-hand panel, click its line in the list. This adds the thread to the set of recently selected threads (history).
To navigate the thread history, use the hyperlinks.
To start a new thread, click the button.
7a. Filled-Form Tables
Both received and sent filled-form results are appended to a table for the associated form type (aka FFN). The filled-form tables are listed in the menu, by date of last update. This menu does not appear if no filled-form results have been recorded. See also Blank Forms.
To view a filled-form table in a separate thread panel tab, open the menu and click the hyperlink for the table.
To view the thread & message that delivered a specific filled-form table entry, click its hyperlink, which adds a ⦒ Form result tab above the message list.
Future versions of mnm will provide filled-form table queries, and operations on query results.
7b. Calendar Events
Event objects attached to received and sent messages are added to the events table. Events may also be uploaded to the table, or downloaded from an iCalender service. [Not yet implemented]
8. Messages
The left-hand panel presents the subject and message list for the most-recently selected thread. If the thread has multiple subjects, the subject field becomes a menu (denoted by ▽) headed by the subject of the most recent message.
After clicking either a row in the thread list or the hyperlinks, the message list may be scrolled with the arrow keys.
The message list displays a header for every message in the thread. The tabs above the list control which messages are open (i.e. fully shown). Open shows messages which the user has directly opened (or in the case of sent messages, not yet closed). All shows all messages.
To show all messages containing a specific tag, select the tag name from the menu, which adds a #Tagname tab (see also Tags).
To show any messages containing a phrase, enter the phrase in the search box and press enter, which adds a tab for the phrase.
Clicking a message hyperlink in a received/sent message or draft preview adds a ⦒ Linkname tab above the message list showing only the referenced message (see also Markdown). If a tab for the referenced message already exists, its title is instead updated to the latest linkname.
To open or close a message, click the message header (contains the date, time, and sender's alias). While a newly-opened message is loading, is displayed.
All attachments received or sent in messages of the thread (except filled-form results) are listed in the 📎 menu. The list may be sorted using the Date, Who, File, and Size tabs.
To view the message that delivered an item in the 📎 menu, click the hyperlink for the item, which adds a ⦒ Attached tab above the message list.
To download a file in the 📎 menu, click its hyperlink. (Note: the □ character is appended to download filenames which may be reserved by the OS.) To preview a file or form, click its name hyperlink. Attachments that can be previewed display a icon.
8a. Recipients
Individuals or groups who have received a copy of the thread are listed in the menu. Those who have unsubscribed from the thread do not receive new messages, and are indicated by Recipient Name . The recipients list may be sorted using the Date, Who, and By tabs. Each list item may include a Note written by the member who added that recipient. To view the Note, mouse over a recipient's name in the list.
To forward a thread to new recipients (or resubscribe unsubscribed recipients), open the menu, edit the Note field to indicate why you've added this recipient if desired, use the Forward to field to select an alias or group, and click the button (or press enter); repeat for other recipients. (Recipients won't receive anything unless the thread is forwarded.) To remove a recipient from the forward, click the × button for the target item. Click the button to forward the thread to the new recipients.
A forwarded thread is delivered without any attachments. These pending attachments are automatically delivered separately by their original senders. This policy prevents the transmission of malware disguised as a benign attachment from a trusted sender. A copy of each message is also automatically delivered by its original sender. Until the copy is received, the sender's name on the message is labeled [unverified], and the label's hover text gives the identity of the account that forwarded the thread. If the original copy does not match the forwarded version, the sender's name is labeled [possibly forged].
8b. Received/Sent Messages
Heretofore unopened messages present the date & author in bold text in the header.
Open messages display the list of attachments, if any, immediately below the header. To close the list, click the 1📎 icon.
To download a file attachment, click its hyperlink. To preview a file or form, click its name hyperlink. Attachments that can be previewed display a icon. To view a filled-form result, click its name hyperlink. Filled-form attachments display a icon. Pending attachments in forwarded threads display a icon.
The Subject field appears as Re: etc, if present. The Message text displays if it's empty.
Both the Subject and Message text may contain Unicode characters, however the typeface in use typically won't include a rendering of every possible character. To accommodate commonly written characters that are omitted in many typefaces, the mnm app varies the typeface within a message [not yet implemented]. Any invalid representations of Unicode that occur are altered by the browser to one or more � symbols. To explore the Unicode universe, visit &what;.
To change the typeface of a message, [not yet implemented].
To reply to a message, click the button to the right of the header.
To reply to a message containing a blank form and include a filled-form result in the reply, click the button above the form. Blank forms are fillable only in the preview panel of the draft editor.
8c. Tags
A message may be assigned any number of predefined and user-defined tags, which allow threads and messages to be found via tag searches. When a tag is sync'd to account replicas, on any device where that tag name is already in use by an account not replicated locally, the tag's name on that device will be changed to tagname [accountN] (see also Replicas).
To set or clear a tag, open the message to be tagged, and use the menu that follows the sender's name in the message header.
To create a new tag, use the New Tag form in the menu.
To remove a tag from all messages where it appears... [not yet implemented] To rename a tag... [not yet implemented]
To list all threads containing a specific tag, use the menu next to the threads panel search box. To show all messages within a thread containing a specific tag, use the menu next to the message panel search box.
8d. Draft Composition
Starting a new thread or a reply within an existing thread opens a draft editor. Its input fields are the Attachment list (optional), the Subject (optional for replies), and the Message text (optional). The Subject (a.k.a. Re) is minimized on replies by default; to edit it, click the thin white bar above the Message text field. Edits to all fields are automatically saved; the Subject and Message text are saved periodically during editing.
New draft threads (but not replies) are automatically tagged with Todo, so appear in the #Todo thread listing. You can remove the tag at any time. Automatic tagging can be disabled in the menu [not yet implemented]. See also Tags.
To preview the Message text as it would look to recipients, press Ctrl-J. The preview panel always appears in the same position, even if there are multiple open drafts in the current message list. A panel that scrolls with the message list is [not yet implemented]. To hide the preview panel, press Ctrl-J again or click anywhere outside the draft's Message text or Subject.
To preview all slides in a slide deck, click the ⟪. . .⟫ hyperlink on the preview panel. To return to normal deck preview, click the hyperlink again.
To name recipients of a new thread, open the menu, edit the Note field to indicate why you've added this recipient if desired, use the To field to select an alias or group, and click the button (or press enter); repeat for other recipients. (Recipients won't receive anything unless the draft is sent.) To remove a recipient from the list, click the × button for the target item.
The draft gives a summary of recipients next to the button. If no recipients have been named, the draft indicates [self].
To attach a file or blank form, click the or ⍰ icon (below the button) to open the appropriate menu, then attach an item by clicking its 📎 button. To display an image or blank form in the Message text, see Markdown.
To drop an entry in the Attachment list, click the list's pop-up menu to reveal its contents, then click the × button for the target item. If an entry in the Attachment list is referenced by Markdown in the Message text, dropping that list item disables the button.
To add/drop a filled-form result to/from the Attachment list, click the attach fill checkbox above the form in the preview panel. If adding a result, also fill in the form fields. (Requires a blank form in the Message text; see Markdown.)
To send a draft, click its button. If it cannot be sent immediately, it is queued, which is indicated by the icon to the right of the header.
To delete a draft unsent, click its button.
8e. Markdown
The Message text supports Markdown, a widely used plain-text shorthand for essential web page features. Markdown avoids the unpredictable behavior of browser WYSIWYG editors. Consult the CommonMark Reference and Github's Markdown Tables for documentation.
To view a summary of Markdown options in mnm, click the tab in the top-left corner of the preview panel. Alternatively, press Ctrl-M while editing a thread draft. Repeating Ctrl-M advances to the next frame of options, then closes the summary sidebar.
mnm extends and amends Markdown as follows:
Images must reference a file in the Attachment list, or one attached to a previous message in the current thread. This prevents the sender from forcing the recipient's browser to contact an Internet site without the recipient's consent.
For a file in the Attachment list, write: ![alt](this_u:file_name)
For a file attached to a previous message, write: ![alt](file_ref)
To obtain the code above, open the 📎 menu,
click the hyperlink for the target file, then paste the
string into your Message text.
alt is any desired text.
Blank Forms must reference a form in the Attachment list, or one attached to a previous message in the current thread. Any blank form with a reference that's already in use is ignored.
For a form in the Attachment list, write: ![?](this_f:form_name)
For a form attached to a previous message, write: ![?](form_ref)
To obtain the code above, open the 📎 menu,
click the hyperlink for the target form, then paste the
string into your Message text.
Hyperlinks may reference a remote site, or a file attached to a message in the current thread, or a previous message in any thread. References to relative URLs are disallowed.
For a remote site, write: [text](url)
For a file in the Attachment list, write: [text](this_u:file_name)
For a file attached to a previous message, write: [text](file_ref)
For a previous message in any thread, write: [text](msg_ref)
To obtain the attachment code above, use the 📎 menu
as previously described.
To obtain the message code above, open the target message, click the hyperlink
to the right of the header, then paste the string into your Message text.
text is any desired text.
Charts [not yet implemented]
Slides let you format a message (or portion thereof) as a deck of slides.
Among other benefits, slides can be used to present a lot of information
without immediately intimidating the reader.
To create a slide deck, write:
optional content above deck
:::
first slide content
:::
next slide content
:::>
optional content below deck
The :::> is unnecessary if no content follows the deck.
A message can contain any number of separate decks.
9. Notices & Errors
New updates to an account are noted in the menu next to the menu, and in the menus provided for each account on the accounts panel. Notices appear for invites, messages, new threads, group membership changes, and [more]. A count of new notices appears to the left of the menu icon. [partially implemented]
If a notice has a blurb, an ellipsis (. . .) appears within it. To show or hide the blurb, click anywhere on the notice.
To show a subset of notices, click the • invites (etc) hyperlinks at the top of the menu.
To mark all new notices as seen and drop any previously seen notices older than one week, click the button at the top of the menu. The period for which seen notices are retained is configurable [not yet implemented].
If mnm encounters an error, a appears beside the top menu, and an error message is posted to the section within the menu. That section is shown when you open the menu after an error occurs.
10. General Configuration
General configuration for the mnm app is listed in the menu.
To accept an account replica from an mnm app running on a different device, click the button. See Replicas for complete instructions.
11. Documentation
Documentation and the product version string are provided in the menu.
12. Credits
The mnm app and TMTP server are written in Go. The app also depends on Couchbase-sponsored Bleve, and Gorilla Websocket. The server also depends on Brett Vickers' NTP client.
The browser-based UI depends on Vue.js for HTML templates and components, UIkit for stylesheets, markdown-it for Markdown rendering, Luxon for Date/Time formatting, and vue-form-generator for the Blank Forms feature.
mnm is always tested first with Mozilla Firefox, and the author frequently turns to the MDN web docs. Development of mnm is managed with Git.
The logo is by David Gilmore.
Robin Eng tested the preview releases at length, helping the author validate his concept, and discover a multitude of minor flaws.
13. License
Copyright 2018, 2019 Liam Breck
Published at
https://github.com/networkimprov/mnm-hammer
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at
http://mozilla.org/MPL/2.0/