Published on

Benefits of a text-based (web) app

Authors

The functionality of markwhen starts from the source: the input text.

Input string

Input string

When the user types something, the text is (re)parsed, and the resulting timeline information is fed to the view to display.

The user can then edit the raw text via the editor, or perform actions via the UI that will edit the text on their behalf, causing the cycle to continue.

It's all in the text

You only have one source of truth - the text. Everything else is a transformation thereof.

If we expand some of the boxes in our diagram, we can see that we have separate steps, each of which takes input from the last and does its own work on top of it. Because I'm using vue, these are computed properties, which is perfect as they only update when their dependencies change.

pageStore.ts

This here is an example of one of the (pinia) stores that does some transformations. For readers more familiar with react and redux, pinia is like redux except it isn't fucking convoluted and needlessly complex.

You can see that we're relying on outputs from the markwhenStore to produce new outputs which will be further utilized downstream.

Note the lever 🕹 emoji, which we will come back to.

ts
import { defineStore } from 'pinia'
import { computed, ref, watch, watchEffect } from 'vue'
import { useMarkwhenStore } from './markwhenStore'

export const usePageStore = defineStore('page', () => {
  const pageIndex = ref<number>(0)
  const markwhenStore = useMarkwhenStore()

  /* 🕹 */ const setPageIndex = (index: number) => (pageIndex.value = index)

  const pageTimeline = computed(() => markwhenStore.timelines[pageIndex.value])
  const pageTimelineMetadata = computed(() => pageTimeline.value.metadata)
  const tags = computed(() => pageTimeline.value.tags)

  const pageTimelineString = computed(() =>
    markwhenStore.rawTimelineString.slice(
      pageTimelineMetadata.value.startStringIndex,
      pageTimelineMetadata.value.endStringIndex
    )
  )

  return {
    // state
    pageIndex,

    // actions
    setPageIndex,

    // getters
    pageTimeline,
    pageTimelineMetadata,
    pageTimelineString,
    tags
  }
})

🕹 Levers

What's nice about this setup is that you can allow users' to have levers into each of these transformations, and they will propagate nicely downstream, without affecting whatever is upstream.

🕹 Edit text
🕹 Set the current page
🕹 Sort and filter
🕹 Pinch, zoom, scroll

In the Set the current page example, the variable pageIndex from the pageStore is changed, which updates the pageTimeline, pageTimelineMetadata, and tags, which feed into the next transform that needs them.

What this gives us

A nice separation of concerns, clear, explicit dependencies, and modularity for introducing new features. Either a new feature fits nicely into what a transform is already doing, or maybe we need a new one and then we just have to choose where to insert it.

What is actually being described

Maybe I'm just trivially describing reactive frontends like vue/svelte/react in general. Though even with those frameworks it can be tempting if not necessary to work outside the nice loop that we have drawn.

To me it feels like functional programming; simple inputs and outputs. State is a bitch, and if you have a nice flow that definitively starts somewhere (the text) versus imperatively trying to reason stuff out, things tend to fall into place nicely (obviously this is a bit disingenuous, in both cases things are reasoned about, but it doesn't feel like it as much in the former case).