Skip to content
Sven edited this page Jan 31, 2018 · 32 revisions

Feature discussion

Collection with meta data

class Collection:
    """
    .. describe:: count

       Number of items in the collection (that are not trashed).
    """

    def __init__(self, host, meta):
        # ...
        self.count = meta['count']
        self.author_ids = meta['author_ids']

    @property
    def authors(self):
        return self.app.r.omget(self._author_ids)

    def add(self, item):
        """Subclass API."""
        self.app.r.rpush(self.map_key, item.id)
        self._update()

    def json(self, restricted=False, include=False):
        return {
            'count': self.count,
            **{'author_ids': self._author_ids},
            **({'authors': [a.json(restricted, include) for a in self.authors]} if include else {})
        }

    def _update(self):
        active = [item for item in self.values() if not item.trashed]
        self.count = len(active)
        self.author_ids = list(set(author.id for item in active for author in item.authors))
        self.app.r.oset(self.host[0].id, self.host[0])

class Trashable:
    def __init__(self, trashed, collection=None, activity=None):
        # ...
        self.__collection = collection

    def trash(self):
        # ...
        if self.__collection:
            self.__collection._update()

    def restore(self):
        # ...
        if self.__collection:
            self.__collection._update()

Event templates

Object.assign(this.eventTemplates, {
    "purr": ".foo-purr-event-template"
});
this.eventData = {makeURL: foo.makeURL};
let elem = document.importNode(ui.querySelector(ui.eventTemplates[event.type]).content, true);
micro.bind.bind(elem, Object.assign({event}, this.eventData));
li.appendChild(elem);

Roadmap outline

  1. MVP / Beta 1:
    • Target: App is usable in a minimal form. User mistakes are correctable.
    • Audience: Groups the developers are themselves part of
    • Feedback channel: Direct
  2. Beta 2:
    • Target: App is usable on a daily basis. Core functionality is implemented. Purpose is clearly apparent to first time users. Feedback from Beta 1 is incorporated.
    • Audience: Social networks of the developers
    • Feedback channel: Social networks / App
  3. Beta 3:
    • Target: App monitoring is available. Feedback from Beta 2 is incorporated.
    • Audience: Public, announced on various platforms, e.g.:
    • Feedback channel: App / Platforms
  4. Stable:
    • Target: Features expected from a self-contained app are implemented. Feedback from Beta 3 is incorporated.

Watchable.unwatch()

unwatch(prop, onUpdate) {
    let i = (watchers[prop] || []).findIndex(f => f === onUpdate);
    if (i === -1) {
        throw new Error();
    }
    watchers.splice(i, 1);
}

Data binding

Sophisticated user listing:

<p data-title="joinText list.authors ', ' map 'name'">
    <span class="fa fa-pencil"></span>
    <span data-content="join list.authors 'user' ', ' 3">
        <template><micro-user></micro-user></template>
        <template data-name="overflow">
            + <span data-content="sub list.authors.length 3"></span>
        </template>
    </span>
</p>

Switch:

<span data-content="switch user.role 'user' 'staff'">
    <template><button>User action</button></template>
    <template><button>Staff action</button></template>
    <template>Nothing to do</template>
</span>

<span data-content="switch user.staff true">
    <template><button>Staff action</button></template>
</span>

Format:

<span data-content="format 'Hello {user}!' 'user' user.name">
<span data-content="format 'Hello {user}!'">
    <template name="user"><micro-user data-user="user"></micro-user></template>
</span>

Translate (calls format):

<span data-content="translate 'hello' 'user' user.name">

Advanced translation example:

<p>
    <span data-content="translateEditedBy list.authors">
        <template data-name="authors">
            <span data-content="join list.authors 'user' ', ' 3">
                <template><micro-user></micro-user></template>
            </span>
        </template>
    </span>
</p>
<script>
    this.data.translateEditedBy = (ctx, authors) => {
        let n = authors.length;
        return micro.bind.translate(ctx, n > 3 ? "edited-by-overflow" : "edited-by", "n", n);
    }
    {
        "edited-by": "edited by {authors}",
        "edited-by-overflow": ["edited by {authors} and {n} other", "edited by {authors} and {n} others"]
    }
</script>

Update strategies

  • Python / JavaScript API:
    • Breaking changes okay
    • User decides update time
    • Sometimes non-breaking changes are not possible or only with disproportionate effort:
      • (JavaScript) Convert <custom-element> from/to <div is="custom-element"> (can only be registered once)
  • Web API:
    • Breaking changes only after deprecation period
    • Service provider decides update time, user needs time to make adjustments

Idea for non-breaking update path for function signatures:

def foo(a, b):
    # ...
# ->
def foo(a, x, b=None, __version__=1):
    if __version__ == 1:
        b, x = x, 'default'
    else:
        if b is None:
            raise Error()
    # ...
# ->
def foo(a, x, b, __version__=2):
    # ...

New features and API stability

Assume that micro introduces a new announcement feature with the following API:

  • type Announcement
  • Attribute Application.announcements
  • DB key announcements
  • endpoint /api/announcements
  • page /announcements
  • ...

If an existing micro application already makes use of any of those names, this could lead to breaking behavior (e.g. micro code modifies Application.announcements or posts to /api/announcements and they have different, unexpected semantics).

How can we solve this? New features that involve the extendable API could be major releases. New features could be introduced behind a feature toggle with a minor release and enabled by default with a major release. DB keys could be prefixed with micro.

Linting

Rules that may be worth considering:

Clone this wiki locally