Release process
===============

- Announce on makandra cards
- Update https://makandracards.com/makandra/54158-html-forms-with-multiple-submit-buttons
  - Update Card
- Update better_errors card
  - should use up:fragment:loaded
  - should use request.loadPage() instead of request.navigate()
  - Update card
- Update Card and Slide for Testing config (maxRequests => concurrency, preloadConcurrency, disable preloading entirely)
  - Update card
- Make a 1.0.1 release
  - With Adam's backports
  - With the X-Up-Version header


Remaining feature work
======================

- Following a drawer link on unpoly.com renders an error: guide-36f06c62.js:8 Uncaught (in promise) up.Failed: Viewport has no visible area
- Tansition issue in Github
- Doc Pages without Features are collapsed
- Popups: For styling it would be nice to set [up-origin=target-of-origin]
- Popups: It is not possible to define a spacing. Because we always measure the rendered box, then place it right next to the anchor.
  => Take margin into account!


Doc comments
------------



Tests
=====

- Test that we can pass multiple targets as an array: up.render({ target: [ ... ]})
- Test that layer callbacks via option always set the layer as current AND the layer as `this`
- Test that layer callbacks via UJS attribute set the layer as current AND the origin as `this`
- Test that { acceptLocation } (JS API) also takes an array of alternatives instead of a space-separated string
- Test that we can open layers via { content } that do not even know their { location }
  - test that opening such a layer with { history: true } does not break for the initial opening and a fragment update (it might want to render its history to the address bar but has an empty { location })
  - test that closing a child layer of an location-less layer will not break in parentLayer.restoreHistory()
  - test that restoreScroll() doesn't break
  - test that saveScroll() doesn't break
  - test that feedback highlighting doesn't break
- test that layers with { history: false } inherit that settings to child layers, even if the child layer was opened with { history: true }
- Aborting a slow request emits :up:request:recover event
- Test that unsafe links are not preloaded (does that exist?)
- Edge Cases for concurrent Layer Changes:
  - up.render()
    - das neue fragment sieht einen server-sent event
    - ein server-sent event schließt das overlay
      ODER der server schließt das overlay
      ODER die neue location is eine acceptLocation
      => keine exception
      => change promise rejected
      => das overlay ist zu
  - up.layer.open()
    - das neue fragment sieht einen server-sent event
    - ich mache es direkt auf einer acceptLocation auf
      ODER der server-sent event schließt das overlay
      ODER der server schließt das overlay
      => keine exception
      => open promise rejected
      => der layer geht direkt wieder zu
   - up.layer.dismiss() / .accept()
     - ein layer wird geschlossen während er sich öffnet
       => der layer geht ohne animation wieder zu
       => die open promise rejected
    - ein geschlossener layer wird nochmal geschlossen
      => der zweite close aborted
      => der erste close geht zu ende
- Test that when we update a non-front layer, and the server sends `X-Up-Events: [{ layer: 'current', type: 'foo'}]`, the events are emitted on the updating layer
- Test new queue behavior
  - Size vs. preloadSize
  - Aborting of preload requests
- Test up.network.config.requestMetaKeys
- { onLoaded }, up:fragment:loaded
  - Should be preventable, preventing history changes and fragment updates
  - Allows listener to manipulate change options
- History handling per layer
- Test that up.viewport.autofocus() returns true if it focused an element, otherwise false
- Test that up.fragment.all / up.fragment.get takes a { origin } option that defines a layer and allows "&" lookup
- Test that up:fragment:loaded can interrupt fragment update
- Test that up:fragment:loaded is not emitted when preloading
- Test { layer: 'swap' }
- Test that the original [href] or [up-href] of a link are not interpreted as patterns (if they include colons), just [up-alias]
- Test that overlays may have sizes
- Test that when a popup is open, clicking the opener will close that popup
- Test that when a popup is open, clicking outside it will close that popup
- Test that when a popup is open, clicking on ANOTHER popup opener will close the first popup and open the other (no additional "dead" click required)
- Test that a slow click on an [up-instant] modal opener (with mousedown and click a second apart) will not close the overlay we just opened
- Test that we can open multiple nested modals by clicking on links (bugfix where parent event listener would close the third layer)
- Test [up-content], [up-content=overlay]
- test thtat up.reveal should only see obstructions on its laayer
- test that up.reveal should peel by default
- Test that fragment updates peel the targeted layer by default
- Test that { peel: false } will not peel the targeted layer (especially now that up.viewport.scrollAfterFragmentSwap/reveal also peels)
- Test that up.viewport.get() will match the closest OverlayWithViewport
- Test that up.viewport.get() will find a viewport in a parent layer if the element's layer is not an OverlayWithViewport
- Test special overlay fallback:
  - Only for existing overlays we open will also attempt to place a new element as the
    new first child of the layer's root element. This mirrors the behavior that we get when
    opening a layer: The new element does not need to match anything in the current document.
- Test: up:request:fatal is not emitted when a request was aborted
- Test that up.reload takes { params } option: `up.reload('.author-select', { params: result.id }) } }``
- Test that event closers ({acceptEvent, dismissEvent}) can take both an array and a space-separated list of event types
- Test: When a layer is accidentally removed from the DOM through a fragment update, it is either re-attached or dismissed (up.Layer#repair). In any case the stack is consistent after the update.
- Test: History events are emitted on the history-enabled layer closest to the front
- Test: Re-clicking the popup opener while the popup is open should close the popup instead of re-opening
- Test that up:request:load has access to the { preload } attribute
- Test that a modal opener with [up-instant] doesn't immediately close the new layer (because that closes on parent click)
- Test that responses for a closed layer will not update anything
- Test that up.validate will never update another layer, even if [up-layer] is set on the form and the server responds with 500
- Test [up-accept-location="/foo/:user_id"]
- TEst result = await up.layer.ask({ acceptLocation: "/users/:id"})
- Test that the string callback to up-on-accepted can use the context { event, layer, value }
- Test that the string callback to up-on-opening can use the context { event, layer }
- Test that [up-params] will add additional query params to a followed links that already has query params
- Test that [up-params] will add additional payload to a submited form that already has some fields
- Test for up.element.isDetached()
- Test that up-hungry will not activate in a background layer
- Test that when a request was aborted, re-queuing the request will trigger a new network request (rather than returning the aborted version from cache)
- Test focus behavior in layers:
  - A11Y: opening a layer should focus the layer, optionally to [autofocus]
  - User may also set a { focus } option
  - A11Y: closing a layer should focus the element that opened the layer
  - Closing a layer when the opener is unknown: The parent layer is focused
  - Closing a layer when the opener is no longer attached: The parent layer is focused
  - Pressing escape will blur a focused field. Pressing escape a second time will close the layer.
- Test focus behavior when swapping fragments:
  - Focus should be preserved (step.focus ?= up.FocusCapsule.preserveWithin(step.oldElement)
  - User may also set a { focus } option
- Test that we can actually hit another layer if the initial plan failed
  - We had this for a while:
  - @successOptions.layer = successPreview.preflightLayer()
- Test that all events during fragment change set up.layer.current
  - check if up:fragment:inserted sets up.layer.current
  - check if keep events sets up.layer.current
  - check if follow event sets up.layer.current
  - check if compilers set up.layer.current
  - check if compilers when opening a layer set up.layer.current
- Test that layers without history updates still return their current #location
- Test that layers without history updates still set .up-current links
- Change with { reveal: 'selector' } should not find elements in another layer
- Test that accepting/dismissing the root layer will just follow the link
- Test that proxy-events are emitted on their layer, not the document
- Test that proxy-events are emitted a second time on the document if the layer has been detached
- Test and document up.network.abort().
- Test and document up.network.abort(request)
- Test and document up.network.abort(conditionsObj)
- Test that all Change#execute() functions check if they're still applicable (or if the layer has been removed)
- Test that back button closes all layers
- Test that up.render() can be called with { target: Element }
- Test that up.render(), when called with { target: Element }, ignores options that would otherwise select a layer, like { origin }
- Test that up.render() without { layer } option will only find in current layer
- Test that clicking on the layer backdrop will cause dismiss on iOS (https://code.makandra.de/makandra/studyflix/commit/cf7d016a1d00797519b709cfcb27423c6adea9d2)
- Test that peeling is not preventable by preventing up:layer:dismiss
- Test that opening/updating an layer can reset the world if selector is unavailable
- Test against https://github.com/unpoly/unpoly/issues/79 (mousedown removes a layer, click goes to nowhere)
- Test options inheritance in layer hierarchy (any => overlay => mode)
- Test that a layer cannot have history: true if its parent has history: false
- Test that making a layer change in an { onAccepted } callback will not deadlock the acceptance change
- Test that layers are assigned .up-destroyed class while closing
- Test that default targets are selectable per layer-type
- Test that default targets are also fallbacks per layer-type
- Test non-standard failOptions:
  - failOptions: mirror
  - failOptions: inherit
- Test that ResetWorld is actually happening
- Test that up.render({ content: 'foo' }) works when 'foo' is just text, without a wrapping tag
- Test that we can submit a form into a new layer (success / fail)
- Test up.network.abort()
- Test that opening a new layer cancels requests that also open a new layer
- Test that closing a layer cancels all requests for that layer
- Test that up.render(html: Element) works
- Test that forms can submit as arbitrary layer changes (success and failure)
- Test that [up-on-accepted] and [up-on-dismissed] get the link's layer as up.layer.current
- Check that up.layer forwards getters for all layer properties to stack.current
- Test that compilers always see their up.layer.current, even if it is a background update and there is another layer above it
- A11Y: current layer should get [role=dialog] and [aria-modal=true], all parent layers should get [inert] and [aria-hidden: true]. Since the root layer has no containers, we should apply this to direct children
- UpdateLayer should re-create default targets, not only when opening but also when updating!
  - E.g. I have up.layer.config.modal.targets = ['.content', '.menu']
  - Now that layer was opened with .menu
  - The next request only has .content
  - => We should clear out the modal and build a new .content, since that is also what we would do on "new"!
  - => In case that the current modal DOES have .content we should update this instead of re-rooting
- up.Params#has

Docs
====



Later?
------

- When we are already on #hash and we click on another link to #hash
  - there is no #hashchange event
  - hence revealHash() is not run
  - hence we don't fix the browser's scroll position to scroll past obstructions
- Support up-poll=true, up-poll=auto, startPolling({ enabled = true })
- An up:app:booted event (now removed) would be useful for something like DeviceClasses, which is only available after the initial compilation
- Publish [up-base-layer] and { baseLayer }
  - In up.render()
  - In a[up-follow]
  - In up.submit()
  - In form[up-submit]
- Allow servers to respond with 304 not modified
  - Automatically sets X-Up-Target: :none
  - Resolve the promise
- Die { cache }-Option aufteilen
  - { readCache: 'auto' }    auto oder true: wenn cacheable
  - { writeCache: 'auto' }   auto oder true: wenn cacheable
  - { clearCache: 'auto' }   auto: clearCacheScope wird immer angewendet, außer Server schickt X-Up-Clear-Cache
  - { clearCache } ist immer auf auto
  - { cache: true } setzt { readCache: true, writeCache: true }
  - up.reload() setzt { writeCache: true }
  - Weiterhin automatisch:
    - { writeCache: true } speichert keine unsafen requests
    - { writeCache: true } speichert keine requests mit target: ':none'
    - { writeCache: true } speichert keine failed requests
    - evtl. auch konfigurierbar machen mit config.isCacheable: (request) -> ...
  - wir brauchen dann [up-read-cache], [up-write-cache], [up-clear-cache]
  - { clearCache } müsste per default an sein, auch ohne { navigate }
  - If we make a request without caching, should we remove a matching cache entry?
    - E.g. we navigate to /foo
    - Then we reload /foo through polling
    - But polling does not write to the cache, since reloading doesn't cache
    - I navigate to /bar
    - I navigate to /foo
    - I see the old version
- up.syntax => up.behavior
  - up.hello into up.behavior
- Is there a use case for this?
  - form[up-accept], form[up-dismiss]
    - Resolution value is { params, ... params.toObject() }
    - existing [up-accept] is then only a[up-accept]
  - form[up-emit]
    - Event value is { params, ... params.toObject() }
    - existing [up-emit] is then only a[up-emit]
- up.render() should resolve to an up.RenderResult that shows me what changed ({ fragments: [...], layer: ... }
- { history: 'force' } (render history in layers that prevent it)
- Can we offer up.idle?
  - No animations running
  - No scrolling running
  - No pending requests
  - All compiled
  - Offer to hook other code into this
- allow link_to ..., up: { target: ... }
  begin
    prefixes = ActionView::Helpers::TagHelper.const_get(:TAG_PREFIXES)
    prefixes << 'up'
    prefixes << :up
  rescue NameError
    # Rails 3.2 does not define that constant
  end
- Fragment changes should support [up-on-appeared] and [up-on-removed] attributes
  - But then we also need [up-then]?
- We cannot use `eval()` or `new Function()` with CSP.
  - new Function will throw an EvalError (both e instanceof EvalError and e.name == 'EvalError')
  - at least we need to report a more helpful error
  - consider adding [up-nonce] and { nonce }
- Abort Up requests if the user navigates away
  - Request#loadPage() already does this, but what about other links?
- We could allow up.fragment.config.runInlineScripts again. I don't know why we removed it, now that we have HTMLWrapper.
  - it might be inconvenient when we fall back to replacing <body>, and the user has their <script> tags before </body>
- When we go back from a non-Unpoly page to an Unpoly page, scroll positions are not restored
  - Test case: https://glitch.com/edit/#!/gaudy-nettle-ash?path=index.html%3A32%3A46
  - Why did we disable scroll restauration in https://github.com/unpoly/unpoly/commit/8b22fef9f72ea316f1a855b36abb6d01263d6059 ?
  - Does the browser have any default scroll restauration on popstate handled by a JS app?
  - If we keep the manual scroll restauration we need to keep our lastScrollTops in a way that it survives full page loads (it's currently a variable in memory)
- Some API would be nicer if up.viewport would return up.Viewport instances that would then have methods like #reveal()
- Should revealSnap also snap to the bottom? This is hard because we don't know the buffer height minus obstructions
- Support up.validate(form)
  - This should replace the entire form
- Support up.validate(containerInForm)
  - This should derive a selector from the container
- Support up.validate(element)
  - Allow up.validate(any_element) and this will find the nearest field or form
- Stagger initial compilation with a time budget
- Validate Requests prevents Submit when clicking out of a focused field into submit button
  - https://makandra.slack.com/archives/C02KGPZDE/p1588250760006500
  - https://jsbin.com/fofefokafu/edit?html,js,output
- get up.layer.of() back
- up.validate() is strange to preview a form change
  - maybe offer up.form.preview() and up-preview=".selector"
  - would be useful to preview another field without triggering validations? would this even work in rails, where a lot of logic hangs on validation hook?
  - then in the inspector:
    - def up.preview?; validate? or request['X-Up-Preview']; end
- Instead of normalizing URL strings and carefully making sure to never double-normalize the same string (for performance reasons), have an exposed up.Location object that toString() to its origin URL. Normalizing can happen when comparing.
- Make the { request } available to up:fragment:inserted, so we can have a variant of https://makandracards.com/makandra/79164 that does not react to { preload } events
- When xhr.responseURL is not the requested URL, assume that there was a redirect and the response method is now GET
- Allow shortcuts like up-any-target that set both success and fail options (if up-fail-options is not sufficient)
- Allow placement of body in overlays with <up-body>
  - Then <body> can really be a default target for all fragments
  - Is this really a good idea? It would break for all pages that use fixed elements in their layout
- Allow per-Layer fragment access like
  - layer.fragment.first()
  - layer.fragment.all()
  - layer.fragment.destroy()
  - layer.fragment.reload()
- Should we track/stop animations and scrolling per layer instead of globally?
- Get rid of up:framework:booted event
- Rename up:proxy:load etc. to up:request:load
- Events like up:layer:child:open
- Does up.remove() clean up jQuery data?
- { peel } option for up.destroy
- Do we want an ExtractPlan.BackButton? Turbolinks keep [up-keep] for back.
- Support [up-target][up-class] and [up-target][up-fail-class] to set a class on the new fragment
  - But think how that would go together with [up-layer="new"][up-class="warning"]
- Support :destroy pseudo-class up up.render() target
- Do we need an API to change context from JS? from server via header?
- Should up.reload / up.render etc. resolve to the updated elements?
- form[up-target] without a target now looks funny
  - I now have form[up-follow] as a workaround
- When up:request:load is prevented, the request should be resolved with an AbortError
- Replace up.Response.isSuccess() with up.Response#ok
- Replace up.Response.isFatal() with !up.Response#body
- Emit up:request:fatal when responses are aborted due to user or timeout
- up:fragment:inserted should receive the entire document from which the fragment was extracted
- Support named layers
- Better cache hits (uhm really?)
  - When accessing up.target etc. the server should track this and respond with
        X-Up-Tailored: ["target", "mode"]
  - We can then use this information to resolve other requests in the queue
  - => But this would only help for requests already in the queue, yes? Because new requests would need to wait what the server responds.
- If all layers have their own implementation anyway, should we re-visit zones?
  - <a href="/foo" up-target=".bar" up-mode="zone" up-anchor=".slot"> (oder up-orgin? naa...)
  - -<div class="slot">
        <up-zone>
          <up-zone-content></up-zone-content>
        </up-zone>
     </div>
  - Should the term "layer" be renamed with "zone" everywhere?
  - This might be more useful if we had multiple children per layer
- Habe ich / will ich up-accept für forms? E. hätte das nicht gebracht, da er erst nach abspeichern
  - up-dismiss-now
  - up-dismiss-after
  - up-dismiss-with-result
    - aber was ist dann das ergebnis? ich könnte hier nur ein leeres ergebnis geben
- Allow to disable validations globally for tests
  - up.form.config.validateEnabled
  - for this it would be nice to distinguish "update dependent field" and "live validations"
- focus: follow-scroll
- To support async scripts, should we run compilers that were added after booting?
- Replacing removed elements should fail
  - It may be a better behavior for a fallback-less fragment update to fail if the targeted fragment was detached while the request was in flight
    - We may even cancel the request?
  - E.g. when a validation request response and the field was replaced in the meantime, discard the response
  - How would this play together with fallback targets?
    - If all fallback targets were removed, the replacement fails
    - e.g. up.render({ target: '.target' })
  - This would also allow us to not process the cascade twice
    - Except when the server changes the target
  - How would this play together with overlays?
    - No such thing as an existing element
    - We would need to stop other requests that open a new overlay
- Cancel the request when its { origin } has been detached?
- Can we not focus if a compiler has focused?
- When clicking on a #hash link in a history-less overlay we should handle the link and not follow but revealHash()
- Replace { revealTop } with { revealAim: 'top' | 'center' | 'min-motion' }
- Request should copy context and cacheKey once queued
  - Then we can clean up context and failContext
- Allow to configure browsers that will not load Unpoly


Decisions
---------

- It is not possible to progressively enhance an [target=_blank] with [up-layer=new]
  - Because Unpoly won't follow an [target=_blank]
  - Alternative: Users could install a macro that removes [target=_blank] when there is also [up-layer=new]
  - Alternative: Users could remove a[target] from link.config.noFollowSelectors
- Do validation targets still need to use :origin (former "&"), now that we consider origin?
  => Yes, because a <fieldset> may occur multiple times in the response document and we cannot consider origin there
- Entscheiden ob ich die [up-modal], [up-drawer], etc. Shortcuts behalten möchte
  - Für Docs?
  - Verwirrend dass es mehrere Möglichkeiten gibt, das gleiche zu machen?
  - up-layer="new"
  => Ja, behalten
- The up.layer package object should be a looked up to "current", since TK naturally expected this to be the current layer
  => Das bringt nichts, dann kann man ja eben nicht sich was wergmerken
- { flavor } umbenennen in { mode }, { scheme } oder { interface }
  - mode: Evtl. habe ich später mal nicht-modale Layer?
  - mode: 'modal' ist doof
  => Es ist nichts wirklich besser
- Should [up-href] be [up-url] to match { url }?
  - No, it matches HTML5 or XHTML2
- Params
  - Separate { query } and [up-query] options
  - NEVER move URL query to payload params
  - GET submissions: Form values should override, not append (set, not add) values from query
- Do I really want up.Change.ResetWorld instead of <body> as root's default target?
  - Yes, if we cannot match an overlay target we want to reset the world
  - One could argue that we only need ResetWorld when we're creating a layer or updating an overlay
- Should we allow opening <body> in a layer through an element <up-layer>?
  => No, this would open fixed nav bars in a layer
- Should up.emit without an element emit on the document or on the current layer? => Keep it on document, as it is now!
- Should we keep up.history.popTargets?
  - up.config.root.targets might not be appropriate
  - but the layer targets will always be options after .popTargets, so we can keep popTargets as empty
- up.fragment.config.fallbackTransition wieder rein?
  => Nein, man kann ja das fallback als { objekt } machen
- It's stupid to have both { layer } and { mode } when one can set both
  => No, we don't want up.layer.open({ layer: 'modal' })
- Should it be up.accept(), up.dismiss(), X-Up-Accept, X-Up-Dismiss? => No
- Should the default layer be current again?
  => Not now, maybe change that leter
- Decide whether to do up.layer.accept({ value: { flight_id: 5 }}) or up.layer.accept({ flight_id: 5 })
  => We keep the value as first argument for the symmetry to resolve / reject
- Decide whether to still up <up-xxx-frame> to more easily position the dismiss icon
  => No, in popups the <up-popup> is the frame. we can position a dismiss icon in that.
up.emit: Do we really need a { base } option, could this just be { layer }?
  - yes, because { layer } also becomes a prop
- How are we going to document super-overloaded functions like up.emit?
  - The full signature is insane
    - up.emit([target], [eventName], [eventOrProps], [emitOptions]) ?
  - I would rather document
    - up.emit([target], eventType, eventProps)
    - up.emit([target], eventPlan)
    - up.emit([target], event, [emitDetails]) # this might be internal
  - We could do it with separate @function blocks
    - I think there's already some logic for multiple guide elements with the same symbol
    - How would this look in the guide nav? At least we would need to de-dup
    - => Do we copy-paste the GIANT docs for up.emit()?
  - DECISION: We do it like this:
    - up.emit([target], [eventType], [eventProps]) ?
- Should up.render() be named up.replace()?
  - up.replace(selectorOrElement, url, [options])
  - up.replace(selectorOrElement, url, [options])
  => No, We named it change() because it sometimes opens a new layer
- One last time consider whether up:layer:open and up:layer:dismissed/accepted should be emitted on the parent layer
  - The counter-argument was always that someone would do this and be confused:
        layer = up.layer.open()
        layer.on('up:layer:dismissed', ...)
  - Leave it as it is.
  - We can do events up:layer:child:open and up:layer:child:dismissed later
  - Or maybe we do up:layer:overshadowed
- Do we want to continue supporting up.render('.foo') instead of up.render({ target: '.foo' }) ?
  - YES, it's the thing we're changing.
- unpoly-rails
  - after_action:
        if !performed? && (response.headers['X-Up-Accept-Event'] || response.headers['X-Up-Dismiss-Event'])
          head :ok

        -- oder

          response.headers['X-Up-Target'] = ':none'
          head :ok
  => Weiß ich wirklich, dass es hier kein Default-Template gegeben hätte? NEIN!
- Possibly remove :closest
  - Is there a second use case for :closest?
  - I'm not sure if this is really the pattern that we want
    - The link *still* must know about the container outside it
    - Another pattern that we have more success with is like overlays:
      The overlay content does not need to know that it lives in an overlay
    - Maybe we were to quick to dismiss the old [up-zone] idea
      - Yes it was impractical that the zoned content always needed to render the [up-zone]
      - But maybe we don't need to have an absolute selector for both sides if the lookup considers the origin zone
      - We also don't need to support the case that the other side might have many components
        - In any list case (Ambulance, Deskbot), actions would update from the member resource, not the collection
    - What I really want is box in multiple copies of a partial.
      Any links should default to updating the box instead of the layer :main

      <up-frame>
        <div class="inner"> <!-- up.frame.firstSwappableElement() -->
          <a href="/foo" up-follow> <!-- will update this .inner, even if other .inners are in the same page -->
        </div>
      </up-frame>

    - How do we break out of the frame?
      - Do we need this?
      - [up-frame=parent]?
- Should we use [up-etag] and If-None-Match ?
  - No, HTTP Caching Infrastrcture is not fragment-aware (would need to vary by X-Up-Target)
  - How would we signal reloading otherwise?
    - Do we need to?
- Should { hungry: true } be navigate only? => no
- Failed form submissions update the form, which is no main target and does not scroll or focus => ok, but what should we do?
- It would be great to have up.fragment.currentOrigin in all callbacks, but that's a lter change
- When opening a new overlay, is the animation option { animation } or { openAnimation }?
  - Are all animation options parsed by link.follow?
  => it's just { animation }

