- Apr 2023
-
web.hypothes.is web.hypothes.is
-
Adhere to the team’s consensus-based process, but use just barely enough process. We should be aided by our tools and processes, not captive to them—it’s part of our job as a team to help find the right balance.
-
-
mailchimp.com mailchimp.com
-
Automatic unsubscribe footerIn Sending Defaults, select Add Unsubscribe Footer to automatically add the footer to your emails. This setting will apply globally, but if you’d rather include it conditionally, use the rules engine. The footer includes information about the address the email was sent to, along with an unsubscribe link. You do not need a website to process these unsubscribes—Mailchimp will direct the recipient to an unbranded web page confirming that the recipient address has been unsubscribed.
-
- Dec 2022
-
en.wikipedia.org en.wikipedia.org
-
it is suggested that editors print as many articles as possible
This is a test annotation.
-
- Aug 2022
-
web.hypothes.is web.hypothes.is
-
Write clean code with minimal cleverness Comprehensibility and resilience far outweigh cleverness. Arcane solutions are not a badge of honor, and understanding your code shouldn’t be a hazing ritual. Don’t be afraid to be boring. New problems do not typically require new technology, nor new architectures. Consistent code helps us ship quickly and sensibly: common conventions and patterns speed everyone up. Experienced team members can understand and extend such code with greater ease, and new developers can get up to speed in a project faster.
-
1. Ship quickly, but sustainably Ship useful stuff to our users frequently, but responsibly, by way of small iterations and resilient conventions. Keep your changes small Small pull requests (PRs) lead to stronger code, faster iteration, sustainable velocity and a happier team. Smaller PRs with well-structured commits are a kindness to your reviewers, who benefit from a lighter context-switching demand. Code review cycles are speedier and of higher quality: reviewers are able to evaluate the entire change, quickly, without undue cognitive fatigue or LGTM-overwhelm. Smaller changes sharpen your focus, encouraging modular code with a purity of purpose. This in turn elicits clear code organization and can make coding and test-writing faster. Iterate, deploy, repeat Frequently ship iterative changes, using a reliable and non-intimidating deployment mechanism. Smaller releases make it easier to pinpoint a problem should something go awry, and are more straightforward to verify in our QA step. Deploying more often gets features and fixes to users fast, and avoids making giant leaps in (potentially) the wrong direction.
-
- Jul 2022
-
web.hypothes.is web.hypothes.is
-
Review with care and humanity Code reviews are business-critical—all production code requires review—and an important opportunity to support your teammates. Everyone involved, whether author or reviewer, has one core objective: help to move things forward. As a reviewer, you are a partner in the success of the body of work. Make code reviews a top priority, and be thorough and compassionate. Commit yourself when you review: focus and take your time. Avoid dismissive terms like “simply”, “just.” Ask questions. Discuss the code, not the coder. There are many conventions and techniques at play in high-quality code reviews, but all involve common underpinnings: be responsive, be committed and be kind. If you’re the code author, set your reviewer up for the best possible review cycle. Roll out the red carpet with a polished PR description. Do everything you can so that your reviewer can get down to the work of reviewing without fuss. Screenshots. Testing instructions. Comments to help them navigate. Remember to expect feedback—that’s the whole point—and to respond to it with the same humanity you expect from your reviewer.
-
- Mar 2021
-
web.hypothes.is web.hypothes.is
-
Don’t be a back-seat coder This means holding off, and not requesting many of the code changes that you may be tempted to request. Feedback that asks for too many changes and feels like a rewrite can be demoralizing, so consider your suggestions carefully and pick the best ones. Yes, one of the aims of code review is to find and correct bugs and design issues with the code. But don’t use code review to try to get the author to rewrite the code to the way you would have written it yourself. Remember that many things in software are a matter of opinion, multiple solutions that each have their pros and cons. For each “correction” that you want to suggest ask yourself whether it might be just a difference of opinion? In cases where the author has considered different solutions and chosen one solution for a reason, consider empowering the coder and respecting their right to make that decision.
-
- Jan 2021
-
qa.hypothes.is qa.hypothes.is
-
Hypothesis to choose from:
Testing
-
- Oct 2020
-
hypothes.is hypothes.is
-
TESTING
-
- May 2019
-
community.canvaslms.com community.canvaslms.com
-
LTI + API Approach
This approach would only work in Canvas
-
Create an assignment in CanvasChoose “External Tool” as the Submission TypeChoose the tool from the list that appears in the modalA properly configured tool allows the instructor to then choose a resourceTool returns a content item message to Canvas with the Launch URL to that resourceTeacher saves and publishes the assignment
We already have all 6 steps of this workflow
-
- Feb 2019
-
hueniverse.com hueniverse.com
-
OAuth 2.0 offers little to none code re-usability.
^ The lead author and editor of the OAuth 2.0 spec
-
- Aug 2018
-
www.seanh.cc www.seanh.cc
-
Return values of methods are still unspecified MagicMocks. For example the code won’t crash in the tests if it tries to call a method that doesn’t exist on the return value of an autospec’d mock’s method: >>> class Foo(object): ... def some_method(): ... return 23 ... >>> mock_foo = mock.create_autospec(Foo, spec_set=True, instance=True) >>> mock_foo.some_method() <MagicMock name='mock.some_method()' id='139778195517520'> >>> >>> # This should crassh but doesn't: >>> mock_foo.some_method().method_that_does_not_exist() <MagicMock name='mock.some_method().method_that_does_not_exist()' id='139778195121168'> Tests can fix this by specifying the return value: >>> mock_foo.some_method.return_value = 23 >>> mock_foo.some_method() 23 >>> mock_foo.some_method().method_that_does_not_exist() Traceback (most recent call last): ... AttributeError: 'int' object has no attribute 'method_that_does_not_exist' But now some duplication between the tests and Foo has been introduced. Again, if the real Foo.some_method() is changed then the above mock code would also need to be updated otherwise the tests for the code that uses Foo could still be passing even though the code is now wrong.
-
Return values of methods are still unspecified MagicMocks. For example the code won’t crash in the tests if it tries to call a method that doesn’t exist on the return value of an autospec’d mock’s method: >>> class Foo(object): ... def some_method(): ... return 23 ... >>> mock_foo = mock.create_autospec(Foo, spec_set=True, instance=True) >>> mock_foo.some_method() <MagicMock name='mock.some_method()' id='139778195517520'> >>> >>> # This should crassh but doesn't: >>> mock_foo.some_method().method_that_does_not_exist() <MagicMock name='mock.some_method().method_that_does_not_exist()' id='139778195121168'> Tests can fix this by specifying the return value: >>> mock_foo.some_method.return_value = 23 >>> mock_foo.some_method() 23 >>> mock_foo.some_method().method_that_does_not_exist() Traceback (most recent call last): ... AttributeError: 'int' object has no attribute 'method_that_does_not_exist' But now some duplication between the tests and Foo has been introduced. Again, if the real Foo.some_method() is changed then the above mock code would also need to be updated otherwise the tests for the code that uses Foo could still be passing even though the code is now wrong.
-
-
www.python.org www.python.org
-
Don't compare boolean values to True or False using ==. Yes: if greeting: No: if greeting == True: Worse: if greeting is True:
-
-
www.seanh.cc www.seanh.cc
-
Note: The mock library actually has two very similar classes - Mock and MagicMock. The difference is that MagicMock supports Python’s magic methods whereas Mock doesn’t. This usually isn’t important, but as the mock user guide says it’s sensible to use MagicMock by default. The Hypothesis tests tend to use Mock more often, though.
-
- Sep 2017
-
instructure-uploads.s3.amazonaws.com instructure-uploads.s3.amazonaws.com
-
PostgreSQL 9.6.4 DocumentationTh
Testing
-
- Aug 2017
-
en.wikipedia.org en.wikipedia.org
-
"My Happiness" was released as a single with "My Kind of Scene" as a B-side.
Testing
-
- Jan 2017
-
localhost:8888 localhost:8888
-
y American author Stephen Crane (1871–1900). The story takes place in the small, fictional town of Whilomville, New York. An African-American coachman named Henry Johnson, who is employed by the town's physician, Dr. Trescott, becomes horribly disfigured after he saves Trescott's son from a fire. When Henry is branded a "monster" by the town's residents, Trescott vows to shelter and care for him, resulting in h
Annotation of page two
-
-
localhost:8888 localhost:8888
-
The Jersey Act was introduced to prevent the registration of most American-bred Thoroughbred horses in the British General Stud Book. It had its roots in the desire of the British to halt the influx of American-bred racehorses of possibly impure bloodlines during the early 20th century. Many American-bred horses were exported to Europe to race and retire to a breeding career after a number of US states banned gambling, which depressed Thoroughbred racing as well as breeding in the United States. The loss of breeding records during the American Civil War and the late beginning of the registration of American Thoroughbreds led many in the British racing establishment to doubt t
Annotation of page one
-
- Sep 2016
-
notes.wtk.io notes.wtk.ioReplies1
-
How could we support “load more” in such situations?
Yep, which is both a UI design and a technical implementation problem.
If we've got all the replies in a separate
annotation_replies
db table, and one of the columns in that table is the ID of the thread root annotation that each reply belongs to, then it would be fairly easy to build on top of that an API for offset and limit paginating through a given annotation's replies, wouldn't it?Of course replies being an arbitrarily nested tree rather than a flat list complicates this, both in terms of the UI and the implementation.
-
- Aug 2016
-
notes.wtk.io notes.wtk.io
-
group:K1p4yo
As mentioned in a reply to another annotation, I wonder if we might want the group IDs to be independent across annotation services like the user IDs are, something like
group:xyz@hypothes.is
.(I know that our current group IDs will be unique across all groups generated by our service, including third-party groups, but if there are one day real third-party annotation services run by other people I don't think we'll be able to rely on that, we'll have to namespace groups.)
-
The URL fragment also contains the domain of the annotation service, hypothes.is, as a namespacing element.
So this would be hard-coded into the client code, or be part of the build-time client code configuration, like
#annotations
is now? i.e. the client would look for a#hypothes.is...
fragment to decide whether to activate itself?Just wondering what a "generic" client, that does not belong to any one particular annotation service but can work with many, would use as its top-level fragment namespace. It seems like the annotation client itself would need its own name for this purpose,
#my-universal-annotation-client:...
. Or use the domain name of the client's website where you can download the client from (which is not necessarily an annotation service website, just likemutt.org
is not an email service website).(I think
#hypothes.is:...
is fine for our client, though.)
-
- Feb 2016
-
www.webupd8.org www.webupd8.org
-
Nautilus Type-Ahead Find Feature Enabled By Default In Ubuntu 14.04
How to enable type ahead find ("interactive search") / disable recursive search in Nautilus (this works for me in Nautilus 3.14.2 on Ubuntu Gnome 15.10): just check
org.gnome.nautilus.preferences.enable-interactive-search
in dconf-editor, or in a terminal just do:gsettings set org.gnome.nautilus.preferences enable-interactive-search false
I much prefer the interactive search version, because its a really fast way to navigate with the keyboard by typing the first couple of letters of a file or folder name to jump the selection to that file or folder. For example I open my home folder and type
dr
and the selection moves to my Dropbox folder then I typeEnter
to go into the Dropbox folder.With recursive search typing
dr
would replace the display of all top-level files and folders in my home dir with a display of all files and folders matching a recursive search fordr
starting from my home dir, which is useful if you're trying to find something and you don't know where it is, but much less useful if you know where you're going and want to navigate quickly with the keyboard.With interactive search enabled you can still do a recursive search by typing
Ctrl-f
first.
-
-
jonudell.net jonudell.net
-
[New Requirement: When there's activity for you somewhere, indicate it?]
This whole problem where he couldn't find his group's annotations because he was focused on the public group - it seems to argue for the All view that Dan has been pushing for, doesn't it?
As the default view. The groups (or "scopes") dropdown then becomes a power-user feature rather than an absolutely-necessary-to-even-see-the-annotations feature - you only need to use the dropdown when you want to filter it to show only some annotations and not others.
-
-
rjzaworski.com rjzaworski.com
-
.then((res) => {
Please, just type
result
. -
window.fetch('/api/v1/users')
Just
fetch()
also seems to work. -
Testing API requests from window.fetch
Sinon.js's
fakeServer
works forXMLHttpRequest
but not forwindow.fetch()
. Instead,window.fetch()
is really easy to stub withsinon.stub()
.
-
- Jun 2015
-
tools.ietf.org tools.ietf.org
-
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
This is a nonce (number used only once), a different one is generated by the client for each WebSocket connection it tries to make.
Also, browser will only allow HTTP headers beginning with
Sec-
to be sent by using certain APIs such as the WebSocket API. Some bad JavaScript running in a browser and submitting forms or sendingXMLHttpRequest
s can't send this header, so it can't open a WebSocket. -
1.6. Security Model
This leaves me wondering how login/authorization to websites works over WebSocket. Do clients include a session cookie or authorization header in the opening HTTP handshake, and that user ID is then established for the life of the WebSocket connection?
What about the privacy of the messages sent down the WebSocket - do people layer encryption on top of the WebSocket protocol?
-
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
This number is computed based on the nonce sent by the client (see later for exactly how), so it proves to the client that the server received its handshake request.
-
If the server does not wish to accept connections from this origin, it can choose to reject the connection by sending an appropriate HTTP error code
So the WebSocket server decides what origins it'll accept WebSocket connections from and for example could choose only to connect with JavaScript code served from its own domain.
-
The server is informed of the script origin generating the WebSocket connection request.
WebSocket is an API provided by browsers to the JavaScript code running in the browsers. So when some JavaScript calls the API to make a WebSocket connection, the browser will tell the client what the origin of that JavaScript is. The JavaScript code can't just trivially claim to be from some other origin.
-
- Apr 2015
-
github.com github.com
-
hypothesis.js
hypothesis.js
is injected into the page by embed.js using either the browser's plugin API or (in the case of the bookmarklet) the DOM API. (embed.js
was in turn injected by the browser plugin or bookmarklet).hypothesis.js
is the "bootstrap" code that connects up and starts the various components of the Hypothesis app. -
app: jQuery('link[type="application/annotator+html"]').attr('href'),
Here we find the
<link rel="sidebar" ...
thatembed.js
injected into the page. We pass it into the constructor method of Annotator.Host below. -
window.annotator = new Klass(document.body, options);
Calling the Annotator.Host construct, passing an
options
object including our sidebar link. -
Annotator.noConflict().$.noConflict(true);
Having created our
Annotator
instance and added our custom plugins etc to it, we inject Annotator into the page.
-
-
github.com github.com
-
layout.app_inject_urls
app_inject_urls
is the list of scripts and stylesheets that we're going to inject into the page. This comes from layouts.py, which in turn gets it from assets.yaml.Most importantly these URLs to be injected include a minified version of hypothesis.js.
-
var baseUrl = document.createElement('link'); baseUrl.rel = 'sidebar'; baseUrl.href = '{{ app_uri or request.resource_url(context, 'app.html') }}'; baseUrl.type = 'application/annotator+html'; document.head.appendChild(baseUrl);
Finally, we inject a
<link rel="sidebar" type="application/annotator+html" href=".../app.html">
into the<head>
of the document. This is the HTML page for the contents of the sidebar/iframe. This link will be picked up by hypothesis.js later. -
if (resources.length) { var url = resources.shift(); var ext = url.split('?')[0].split('.').pop(); var fn = (ext === 'css' ? injectStylesheet : injectScript); fn(url, next); }
This loop is where we actually call
injectScript()
orinjectStylesheet()
on each of the resource URLs defined above. -
var injectScript = inject.script || function injectScript(src, fn) {
And we do the same thing for injecting scripts as we did for injecting stylesheets - we either use the function passed in by the browser plugin, or when called by the bookmarklet we fall back on the DOM API.
-
var injectStylesheet = inject.stylesheet || function injectStylesheet(href, fn) {
hypothesisInstall()
will use theinject.stylesheet()
function passed in to it to inject stylesheets into the page or, if no function was passed in, it'll fallback on the default function defined inline here.The default method just uses the DOM's
appendChild()
method, but this method may fail if the site we're trying to annotate uses the Content Security Policy.That's why when we're using one of the browser plugins rather than the bookmarklet, we pass in the browser API's method for injecting a stylesheet instead.
This is why the bookmarklet doesn't currently work on GitHub, for example, but the Chrome plugin does.
-
embed.js
embed.js
is responsible for "embedding" the different components of the Hypothesis frontend application into the page.First, either bookmarklet.js or one of the browser plugins injects a
<script>
tag toembed.js
into the page, thenembed.js
runs.This way the code in
embed.js
is shared across all bookmarklets and browser plugins, and the bookmarklets and plugins themselves have very little code.
-
-
github.com github.com
-
app.appendTo(@frame)
And we inject our
<iframe>
into ... the frame? (@frame
is a<div>
that wraps our<iframe>
, it's defined and injected into the page in guest.coffee). -
app = $('<iframe></iframe>') .attr('name', 'hyp_sidebar_frame') .attr('seamless', '') .attr('src', src)
Finally, this is where we create the
<iframe>
element that is the Hypothesis sidebar!
-
-
github.com github.com
-
embed = document.createElement('script'); embed.setAttribute('src', embedUrl); document.body.appendChild(embed);
Here we construct the actual
<script>
element, set itssrc
URL, and inject it into the page using the DOM's appendChild() method. -
var embedUrl = '{{request.resource_url(context, "embed.js")}}';
The whole job of the bookmarket is to inject a
<script src=".../embed.js">
element into the current page. Thesrc
URL of this script element points to embed.js, another Pyramid template rendered by the server-side Hypothesis app. -
bookmarklet.js
bookmarklet.js
is the Pyramid template (rendered by our server-side Pyramid app) for the Hypothesis bookmarklet. This little bit of JavaScript (after being rendered by Pyramid) is what the user actually drags to their bookmarks bar as a bookmarklet.
-
- Mar 2015
-
www.bloomberg.com www.bloomberg.com
-
Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices.
This is a fantastic and very detailed AngularJS style/best practices guide. Used by hypothesis/h.
-
A Vim plugin for visually displaying indent levels in code
Great Vim plugin!
-
def create_api(global_config, **settings):
The separation isn't complete yet, but it's our aim to split Hypothesis into two separate Pyramid apps: the API and the frontend.
create_app()
above calls thiscreate_api()
function to add the API features into the app ifh.feature.api
isTrue
in the config file. If it'sFalse
you'll run the frontend only with no API.There'll also be an
h.api_url
setting that you can use to tell the frontend app where to find the API. Ifh.feature.api
isTrue
then you don't needh.api_url
, the frontend can just use its builtin API. But if it'sFalse
then you can useh.api_url
to tell your frontend app to work with an API hosted elsewhere. -
return config.make_wsgi_app()
Finally, we return the configured WSGI app object that Pyramid will call on to respond to any HTTP requests that come in. This is an instance of a Pyramid-provided class that implements the WSGI interface.
-
if config.registry.feature('accounts'):
Some Hypothesis features can be turned on and off in the config file.
-
config.include('.features')
config.include() is Pyramid's way of doing extensible apps. It looks for an includeme() method in features.py (or in
features/__init__.py
if features were a package) and calls it, passing in a configurator object.Since
includeme()
functions get called by Pyramid at startup time they can run any code they want at application startup - for example to verify the values of config settings and crash if they're invalid.includeme()
functions can add anything they want to the config, including routes, views, and subscribers.In Hypothesis we try to implement as many features as possible in standalone modules or packages and include them like this.
-
This means that when Pyramid triggers its
BeforeRender
event our add_renderer_globals() function will be called. (See Pyramid Events). -
config.set_root_factory('h.resources.create_root')
The root factory is a function that returns the root object for Pyramid's traversal tree. (The traversal tree is how Pyramid maps URLs to view callables.)
-
config = Configurator(settings=settings)
A Pyramid app is configured by creating a Configurator object and setting configuration options on that. We'll later use the Configurator object to make a WSGI app with our configured settings.
-
def create_app(global_config, **settings):
This function is referenced as the "main" app factory function in setup.py. Pyramid calls this function at boot to create the main WSGI app.
-
"""The main h application."""
-